Description: https://images.manning.com/360/480/resize/book/2/1ba152c-d7aa-484d-b67f-cefd13c977b0/Siriwardena-OpenID-MEAP-HI.png

From OpenID Connect in Action by Prabath Siriwardena

In this article, you’ll learn how an OpenID provider transports an ID token to a client application using the implicit flow. The sequence of events or steps happens during this flow, as well as the messages being passed in each step is clearly defined in the OpenID Connect specification.


Take 40% off OpenID Connect in Action by entering fccsiriwardena2 into the discount code box at checkout at manning.com.


The flow of events in the implicit authentication flow

Figure 1 shows the sequence of events happens between the OpenID provider, the client application, and the user. The client application in figure 1 can be any type of an application, but here our discussion mostly focuses on a SPA. Also, the implicit flow is more popular among SPAs than any other application type. In the following sections we discuss in detail what happens in each step in figure 1.


Figure 1: The client application uses implicit authentication flow to communicate with the OpenID provider to authenticate the user.


The client application initiates a login request via the browser

In the step 1 of figure 1, the user clicks on the login link and the client application initiates a login request via the browser. In the case of a SPA, we can expect that the user clicks on a login link on the web page of the client application, and browser does an HTTP GET to the authorize endpoint of the OpenID provider.

The authorize endpoint of the OpenID provider is a well-known endpoint and the client applications can find it by going through the OpenID provider documentation or else using OpenID Connect discovery protocol. If you use Google as your OpenID provider, then this is the authorize endpoint of Google, which you can find from their documentation: https://accounts.google.com/o/oauth2/v2/auth.

The request the client application generates in step 1 of figure 1 is called an authentication request. In OAuth 2.0, the request initiated from the client application to the OAuth 2.0 authorization server is called an authorization request.

The following listing shows an example of an authentication request. This is in fact a URL constructed by the client application, which takes the user to the authorize endpoint of the OpenID provider, when the user clicks on the login link.

Listing 1: Authentication request generated by the client application

 
  https://accounts.google.com/o/oauth2/v2/auth? 
       client_id=424911365001.apps.googleusercontent.com&
       redirect_uri=https%3A//app.example.com/redirect_uri&
       scope=openid email&.   #A   
       [email protected]& 
       response_type=id_token token&.   #B 
       state=Xd2u73hgj59435& 
       nonce=0394852-3190485-2490358 
 

#A The scope values are separated by a space. However, when you type this on the browser, the browser will URL encode the space, so the space will be replaced by %20.

#B The response_type values are separated by a space. However, when you type this on the browser, the browser will URL encode the space, so the space will be replaced by %20.

Let’s go through the query parameters added to the authentication request by the client application, as shown in listing 1. The definition of these parameters are consistent across all three authentication flows the OpenID Connect defines, however, the values may change.

  • client_id: This is an identifier the OpenID provider uses to uniquely identify a client application. The client application gets a client_id after registering itself at the OpenID provider. For registration at the OpenID provider, either you can follow an out-of-band mechanism provided by the OpenID provider or use OpenID Connect dynamic client registration API.[1] The client_id is a required parameter in the authentication request, and is originally defined in the OAuth 2.0 specification.
  • redirect_uri: This is an endpoint belongs to the client application. After successfully authenticating the user and getting the consent from the user to share the requested data with the client application, the OpenID provider redirects the user to the redirect_uri endpoint along with the requested tokens (step 5 of figure 1). During the client registration process at the OpenID provider, you need to share the exact URI you use for redirect_uri parameter in the authentication request, with the OpenID provider.

The OpenID provider will do one-to-one matching of the value of the redirect_uri in the authentication request against the one already registered by the client application. Most OpenID providers do an exact match between these two URIs. However, some OpenID providers let the client applications register multiple URI and some let the client applications define a regular expression for the validation of the redirect_uri.

Doing a validation against a regular expression gives more flexibility to dynamically change the redirection path by the client application, but should use consciously and regular expression used for validation must be thoroughly tested.

The redirect_uri is a required parameter in the authentication request, and is originally defined in the OAuth 2.0 specification. However, in the OAuth 2.0 specification the redirect_uri is not a required parameter for both the implicit and authorization code grant types.

scope: The value of scope parameter is just a string, where both the client application and the OpenID provider should be able to interpret the meaning of it. Any OpenID Connect authentication request must carry the value openid for the scope parameter. You can have multiple values for the scope parameter, each separated by space, but one of them must be openid.

The OpenID Connect specification defines four scope values (profile, email, address and phone) in addition to the openid scope. A client application can use any of these scope values to request claims from the OpenID provider.

The scope is a required parameter in the authentication request, and is originally defined in the OAuth 2.0 specification. However, in the OAuth 2.0 specification the scope is not a required parameter for both the implicit and authorization code grant types.

  • login_hint: The value of login_hint parameter is a string that carries some hint with respect to the user (or the application), which can be used by the OpenID provider to build a better user experience. For example, if the application already knows the user’s email address (probably from a cookie stored under the domain of the client application), then that can go as the value of the login_hint parameter and the OpenID provider can directly request the user to share the credentials, rather asking for an user identifier.
  • The login_hint is an optional parameter introduced by the OpenID Connect specification, which you do not find in the OAuth 2.0 specification.
  • response_type: The value of the response_type parameter in the authentication request defines which tokens the authorization endpoint of the OpenID provider should return back to the client application.
  • In the implicit flow there are two possible values: id_token or id_token token. If the value of the response_type is id_token, then the authorization endpoint will only return back an ID token, and if the response_type is id_token token, then the authorization endpoint will return back an ID token and an access token.

The response_type is a required parameter in the authentication request, and is originally defined in the OAuth 2.0 specification.

  • response_mode: The value of the response_mode parameter in the authentication request defines how the client application expects the response from the OpenID provider. This is an optional parameter and is not in the listing 1. If you set the value of the response_mode parameter to query, for example, then all the parameters in the response (from the OpenID provider) are encoded as a query string added to the redirect_uri as shown below.
 https://app.example.com/redirect_uri?token=XXXXX&id_token=YYYYYY

If you set the value of the response_mode parameter to fragment, then all the response parameters are added to the redirect_uri as a URI fragment as shown below.

https://app.example.com/redirect_uri#token=XXXXX&id_token=YYYYYY

In addition to the query and fragment, the OAuth 2.0 Form Post Response Mode (https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html) specification defines another response_mode called form_post.

The response_type and response_mode are bit related to each other. If you do not specify a response_mode parameter in the authentication request, then the default response_mode associated with the corresponding response_type gets applied automatically. If the response_type is id_token or id_token token (implicit flow) for example, then the corresponding default response_mode is fragment (table 1). That means, when you use implicit grant flow, the OpenID provider sends back the response parameters as an URI fragment.

Table 1: The default response_mode values for the corresponding response_type

The of response_type parameter

The default value of response_mode

id_token token (implicit flow)

fragment

Id_token (implicit flow)

fragment

code (authorization code flow, section 3.9)

query

token (OAuth 2.0 implicit grant type)

fragment

The response_mode is an optional parameter in the authentication request, and is originally defined in the OAuth 2.0 Multiple Response Type Encoding Practices specification (https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html), which is developed by the OpenID Foundation (not by the OAuth IETF working group).


Figure 2: The client application uses implicit authentication flow to communicate with the OpenID provider to authenticate the user. This is duplicating the figure 1 for readability purpose.


  • state: The value of state parameter is just a string, which is added to the authentication request by the client application and the OpenID provider must return back the same value (unchanged) in the response (step-5) in figure 2.
  • The state is an optional, however, a recommended parameter in the authentication request, and is originally defined in the OAuth 2.0 specification.
  • nonce: The value of nonce parameter carries a unique value added to the OpenID Connect authentication request by the client application. The OpenID provider must include the value of nonce from the authentication request to the ID token it builds.

The nonce is an optional parameter introduced by the OpenID Connect specification to mitigate replay attacks.

In addition to the authentication request parameters we discussed in the above list, there are few more optional ones: display, prompt, max_age, ui_locales, id_token_hint, and acr_values.

The OpenID provider validates the authentication request and redirects the user back to the browser for authentication

Once the OpenID provider validates the authentication request from the client application, it checks whether the user has a valid login session under the OpenID provider’s domain. Here the domain is the HTTP domain name that you use to access the OpenID provider using a web browser. If the user has logged into the OpenID provider already from the same web browser, then there exists a valid login session, unless its expired.

If the user does not have a valid login session, then the OpenID provider will challenge the user to authenticate (step 2 in figure 2); and also will get user’s consent to share the requested claims with the client application. In step 3 of figure 2 user types the login credentials and in step 4 in figure 2, the browser posts the credentials to the OpenID provider. The steps 2, 3 and 4 are outside the scope of the OpenID Connect specification and up to the OpenID providers to implement in the way they prefer. Figure 3 shows a sample login page, Google OpenID provider pops up during the login flow.


Figure 3: A sample login screen for user authentication from the Google OpenID provider.


The OpenID provider returns back the requested tokens to the client application

In step 5 of figure 4, the OpenID provider returns back the requested tokens to the client application. If the client application, for example, requested only an ID token in step 1, by having id_token as the value of the response_type parameter in the authentication request, then the OpenID provider only returns back an ID token as shown below.

 
https://app.example.com/redirect_uri#id_token=YYYYYYYY&state=Xd2u73hgj59435
 

If the value of the response_type parameter was id_token token, then the OpenID provider will return back both the ID token and the access token as shown below.

 
https://app.example.com/redirect_uri#access_token=XXXXXX&token_type=Bearer&expires_in=3600&id_token=YYYYYYYY&state=Xd2u73hgj59435
 

In both the above cases the OpenID provider returns the tokens as an URI fragment. That’s because, if you do not explicitly mention the response_mode parameter in the authentication request, for the implicit flow the default value of the response_mode is fragment.


Figure 4: In step 5 the OpenID provider returns back the requested tokens to the client application.


Let’s go through the parameters in the URI fragments added to the authentication response by the OpenID provider (step-5 in figure 4). The definition of these parameters are consistent across all three authentication flows the OpenID Connect defines, however, the values may change.

  • access_token: The value of the access_token parameter carries the OAuth 2.0 access token. The OpenID provider adds an access_token to the response only if the response_type is id_token token, under the implicit flow. A client application can use this access token to securely access OpenID provider’s userinfo endpoint to retrieve claims with respect to the logged in user or access a business API. In case the client application does not need to access any OAuth 2.0 secured APIs, it should use just id_token for the response_type. So, there won’t be any access_token in the authentication response.

What you do with an access_token is defined by the scope. If you had email for the scope parameter in the authentication request, for example, then you can use the corresponding access_token to access the OpenID provider’s userinfo endpoint to retrieve logged in user’s email and email_verified claims. If you had address for the scope, then you can retrieve the address claim of the logged in user from the userinfo endpoint.

The OpenID provider (or the authorization server under the OAuth 2.0 terminology) does not necessarily need to respect the scope value in the authentication request all the time. Based on the consent provided by the user and other policies, the OpenID provider can decide which scope out of all the scopes in the authentication request it wants to respect. So, a client application should not expect all the time to get an access token, which is bound to the quested scope values.

If the scope of the access token in the response is different from the requested scope, the OpenID provider must include the corresponding scope value in the response, otherwise the client application can safely assume the token is issued for the requested scope values.

  • id_token: The value of the id_token parameter carries the OpenID Connect ID token, which is a JWT. This is a required parameter.
  • token_type: The value of the token_type parameter carries the type of the OAuth 2.0 access token. This is a required parameter.
  • expires_in: The value of the expires_in parameter carries the validity of the OAuth 2.0 access token in seconds calculated from the time it is issued. This is an optional, but a recommended parameter.
  • state: The value of the state parameter copies the value of the state parameter from the authentication request. The value of the state parameter in response must be exactly the same found in the request. This is a required parameter only if the authentication request carries a state parameter.
  • scope: If the scope of the access token in the response is different from the requested scope, the OpenID provider must include the corresponding scope value in the response, otherwise the client application can safely assume the token is issued for the requested scope values.

One important thing you might have already noticed in the authentication response from the OpenID provider is, there is no refresh_token. A refresh_token is defined in the OAuth 2.0 specification and is used by the client applications to refresh (extend the token expiration) the access_token and the id_token. However, the implicit flow in OpenID Connect does not return back an refresh_token.

If you don’t have a refresh_token, the client application won’t be able to renew the access_token (or the ID token) it got from the OpenID provider, and in that case the client application has to initiate a new authentication request to get a new access_token and a ID token. This is one of the reasons people prefer to use the authorization code flow over implicit flow. We discuss authorization code flow in detail in part 2.


Figure 5: The client application uses implicit authentication flow to communicate with the OpenID provider to authenticate the user.


Once the client application gets the tokens in the authentication response, it can use a JavaScript to extract out the access_token and the id_token from the URL fragment. In practice, what happens is, the OpenID provider does an HTTP redirect (with 302 status code) to the redirect_uri corresponding to the client application and the client application delivers an HTML page with a JavaScript to the browser, which extracts out the ID token and the access token from the URI fragment and does the validation (see figure 5).

The following code listing shows a JavaScript code segment extracted out from the OpenID Connect Implicit Client Implementer’s Guide 1.0 (https://openid.net/specs/openid-connect-implicit-1_0.html) that extracts out the URI fragment from the browser location bar and posts to a backend endpoint for validation.

Listing 2: A JavaScript code that validates the tokens in the URI fragment

 
 <script type = "text/javascript" >
  
 // First, parse the query string
 var params = {},
     postBody = location.hash.substring(1),
     regex = /([^&=]+)=([^&]*)/g,
     m;
 while (m = regex.exec(postBody)) {
     params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
 }
  
 // And send the token over to the server
 var req = new XMLHttpRequest();
 // using POST so query isn't logged
 req.open('POST', 'https://' + window.location.host + '/catch_response', true);
 req.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
  
 req.onreadystatechange = function(e) {
     if (req.readyState == 4) {
         if (req.status == 200) {
             // If the response from the POST is 200 OK, perform a redirect
             window.location = 'https://' +
                 window.location.host + '/redirect_after_login'
         }
         // if the OAuth response is invalid, generate an error message
         else if (req.status == 400) {
             alert('There was an error processing the token')
         } else {
             alert('Something other than 200 was returned')
         }
     }
 };
 req.send(postBody)
 </script>
 

That’s all for this article. Stay tuned for part 2, where we will delve into authorization code flow.

If you want to learn more about the book, check it out on our browser-based liveBook platform here.

 


[1] An out-of-band mechanism could be a developer registering a client application using the UI provided by the OpenID provider.