Browser redirection is an integral part of the Open ID Connect (OIDC) authentication flow. At the same time, it is every UX engineer’s nightmare. Of course, we can do nothing to carry out OIDC authentication without browser redirection. But can we reauthenticate a user without redirection? Yes, but with a catch.
What is reauthentication?
Before we discuss how, let me first explain what I mean by reauthentication. If you are going to store the access token in the browser memory or a web worker like how we do in our Asgardeo SDKs, you will lose the access token on page reload. When that happens, the application has to reinitiate the OIDC authentication flow to obtain another access token. Since usually identity providers use a cookie to track user sessions, we can obtain the access token directly without entering the login credentials if the user has already signed in. However, the user will be redirected to the identity provider’s authentication endpoint briefly during this flow creating a jarring user experience. This is the redirection we are going to avoid.
Why do we need this redirection in OIDC?
First, let’s see why we need this redirection. In the OIDC authentication code grant, we first generate an authorization request URL that contains the client ID, the redirect URL, and the grant type among other things. We then redirect the user to this URL. This URL belongs to the identity server and hence, the browser redirects the user to the identity server’s authorization endpoint. Here, the identity server checks if the user needs to log in and if so, prompts the user for the login credentials. Once the user signs in, the identity server redirects the user back to the application with the authorization code. The application then exchanges this authorization code for an access token. So, the point to note here is that we need to redirect the user to the identity server to get the authorization code.
How can we avoid it?
So, how are we going to avoid it? Well, actually we can’t. But we can hide it. And how are we going to do it? Let’s take a leaf out of how OIDC session management is done. In OIDC session management, we generate an authorization URL and set it to the src attribute of a hidden iframe. However, unlike a normal authorization URL, we add an additional query parameter called “prompt” and set its value to “none”. What this does is it redirects the user back to the application without prompting the sign-in page.
So, if a user is logged in, we get the authorization code in the redirect URL. If the user isn’t logged in, we get an error parameter in the redirect URL instead of being taken to the sign-in page. The application uses this to know if there is an active user session on the server. If there is no active session, we won’t get the authorization code in the redirect URL, so we log the user out of the application. This is how single logout works in OIDC.
The part that is of interest to us in this flow is the part where we get the authorization code in the redirect URL. Users can’t see it when a redirection happens within a hidden iframe. And we get the authorization code too. So, in essence, we get the authorization code without explicitly redirecting the user. All done, right? Not exactly.
The first hurdle
There is a catch. We get the authorization code within the hidden iframe. We can exchange this code for an access token and store it in the session storage within the iframe. This way the application can also have access to the access token. But the whole point of storing the access token in the browser memory or using a web worker is to avoid storing the access token in the session storage. So, this is not even an option for us. Ideally, we should send the authorization code to the main window and have the application do the token request.
So, how do we send the authorization code from the iframe to the main window? Enter postMessage—a method that allows communication between window objects. We can write a logic to send the authorization code to the main window from the iframe using the postMessage method. Then, we can capture this code in the main window and exchange it for the access token.
You may get a very relevant question in your mind here. Do we write a separate logic for the iframe? Yes, you can if you want to. But you can also use your own application to write this logic. When the redirect URL points to your application, your application is going to be loaded within the iframe on redirection. So, definitely, your application can handle this logic.
The second hurdle
However, how do you differentiate between this scenario and a scenario where you have to authenticate a user for the first time? In other words, if the user is not already logged in to the identity server, the user has to enter their credentials into the sign-in page, be redirected back to the application with the authorization code, and then the application has to exchange this code for the access token. During reauthentication, however, the authorization code will have to be sent to the main window from within the iframe. So, the application has to do two different things based on the two different scenarios. But how do we identify the scenarios?
We can use the “state” query parameter to this end. The beauty of this “state” parameter is that we can append this parameter with a value assigned to it to the authorization URL, and we will get this parameter with its value as it is in the redirect URL. If we set “silent-sign-in” as the value of this parameter and append it to the authorization URL and set the src attribute of the iframe to this URL, then when the identity server redirects back to the application with the authorization code, the URL is also going to contain the “state” parameter with its value set to “silent-sign-in”. By checking this parameter, we can know that we have to send the authorization code to the main window.
This way we can reauthenticate users without explicit redirection. We use this method to implement the “trySignInSilently” method in our Asgardeo SPA, React, and Angular SDKs. You can find the implementation of this method in the Asgardeo SPA SDK repository.