How to implement OpenID Connect for single-page applications
The OpenID Connect authentication protocol can be used to secure a variety of applications. This excerpt teaches developers how it works with single-page applications.
The push to shift security left means developers must now consider protecting user and business data through identity and access management when building applications.
One standard developers can use is OpenID Connect, which rests on top of OAuth 2.0. The protocol works with a variety of application types, from popular single-page applications to native web apps and APIs.
To help developers learn how to use OpenID Connect alongside OAuth 2.0, author and identity and access management (IAM) evangelist Prabath Siriwardena wrote OpenID Connect in Action. The book teaches developers how to secure four application types and offers a number of security best practices.
In this excerpt from Chapter 3 of OpenID Connect in Action, Siriwardena explains how to integrate the protocol with single-page applications. Download a PDF of the chapter here, and you can use the code "nltechtarget21" for 35% off the book.
Check out an interview with Siriwardena, where he discusses how to use the book and why OpenID Connect works so well for authentication with different application types.
With the heavy adoption of APIs, over time, single-page applications (SPA) have become one of the most popular options for building client applications on the web. If you are new to single-page application architecture, we recommend you first go through the book SPA Design and Architecture: Understanding Single Page Web Applications by Emmit Scott (Manning Publication, 2015). Also in this chapter we assume you have a good knowledge of OAuth 2.0, which is the fundamental building block of OpenID Connect.
Under OAuth 2.0 terminology, a SPA is identified as a public client application. In principle, a public client application is unable to hide any secrets from the users of it. Most of the time a SPA is an application written in JavaScript that runs on the browser; so, anything on the browser is visible to the users of that application. This is a key-deciding factor on how you want to use OpenID Connect to secure a SPA.
In this chapter we'll teach you what OpenID Connect authentication flows are and how different OpenID Connect authentication flows work with a SPA. You will also learn how to build a SPA using React and then log in to it via OpenID Connect. React is the most popular JavaScript library for developing user interfaces. If you are new to React, please go through appendix A first.
3.1 Authentication flows define the communications between a client application and an OpenID provider
In this section you'll learn what is an authentication flow in OpenID Connect and different types of authentication flows.
In chapter 1, you learnt that OpenID Connect defines a schema for a token (which is a JSON Web Token (JWT)) to exchange information between an OpenID provider and a client application; and a set of processing rules around it. The OpenID Connect specification identifies this token, as the ID token, which we will briefly discuss in this chapter and in detail in chapter 4.
In addition to the ID token, OpenID Connect specification also introduces a transport binding, which defines how to transport an ID token from an OpenID provider to a client application (figure 3.1). In OpenID Connect, we use the term authentication flows to define multiple ways by which you can transport an ID token from an OpenID provider to a client application.
OpenID Connect defines three authentication flows:
- authorization code flow,
- implicit flow, and
- hybrid
In section 3.3 you learn how implicit flow works and in section 3.9 how authorization code flow works. We'll discuss hybrid flow in detail in chapter 6.
3.2 Authentication flows vs. grant types
The OAuth 2.0 core specification (RFC 6749) introduced four grant types, which we discussed in chapter 2 in detail. In this section you'll learn how an OpenID Connect authentication flow relates to a grant type as well as the differences.
A grant type in OAuth 2.0 defines a protocol how a client application can obtain an access token from an authorization server. Typically, a grant type defines four key components (please see section 2.3 for the details): authorization request, authorization response, access token request and access token response.
An authentication flow in OpenID Connect uses grant types, but an authentication flow is more than a grant type (table 3.1). Typically, an authentication flow in OpenID Connect defines four key components, quite similar to an OAuth 2.0 grant type, but not exactly the same: authentication request, authentication response, token request and token response. You might have already noticed the differences; in a grant type we have an authorization request/response, while in an authentication flow we have an authentication request/response, also in a grant type we have an access token request/response, while in an authentication flow we have a token/request response.
The OpenID Connect specification defines the authentication flows in a self-contained manner in itself. So we should not confuse the OAuth 2.0 grant types with OpenID Connect authentication flows. The authorization code flow in OpenID Connect is not as same as the authorization code grant type in OAuth 2.0, and the implicit flow in OpenID Connect is not as same as the implicit grant type in OAuth 2.0.
3.3 How does implicit flow work?
In this section 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.
3.3.1 The flow of events in the implicit authentication flow
Figure 3.2 shows the sequence of events happens between the OpenID provider, the client application, and the user. The client application in figure 3.2 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 3.2.
THE CLIENT APPLICATION INITIATES A LOGIN REQUEST VIA THE BROWSER
In the step 1 of figure 3.2, 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, which we discuss in detail in chapter 12. 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 3.2 is called an authentication request. You may recall from the chapter 2, 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.
Let's go through the query parameters added to the authentication request by the client application, as shown in listing 3.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, which we discuss in chapter 12. The client_id is a required parameter in the authentication request, and is originally defined in the OAuth 2.0 specification, which we discussed in detail in chapter 2.
- 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 3.2). 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. In the section 3.3 we explain the use cases where you want to have multiple redirect_uris for the same client application.
- 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. In chapter 5, we discuss in detail how to use scopes to request claims.
- 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. In chapter 11 we discuss a couple of use cases of login_hint with respect to OpenID federation.
- 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 2.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, and we'll discuss that in chapter 6.
- 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 3.2). That means, when you use implicit grant flow, the OpenID provider sends back the response parameters as an URI fragment. query string.
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).
- 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 3.3. In section 3.5 we discuss the use cases of the state parameter and also in chapter 13 we discuss how to use the state parameter to mitigate some possible security threats.
- 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 section 3.7 explains how to generate a nonce to be unique and nonguessable.
- The nonce is an optional parameter introduced by the OpenID Connect specification to mitigate replay attacks and in chapter 13 we discuss nonce in detail.
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. We'll discuss them in detail in chapter 6.
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 3.3); and also will get user's consent to share the requested claims with the client application. In step 3 of figure 3.3 user types the login credentials and in step 4 in figure 3.3, 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.4 shows a sample login page, Google OpenID provider pops up during the login flow.
About the author
Prabath Siriwardena is a member of technical staff for identity at DevRev. He is a developer, architect and evangelist with more than 18 years of industry experience designing and building critical IAM infrastructure for global enterprises, including many Fortune 100/500 companies. From 2007 to 2021, he led the development, architecture and strategy of the open source WSO2 Identity Server. As an evangelist, Siriwardena has published eight books, including OpenID Connect in Action (Manning), Microservices Security in Action (Manning), Advanced API Security (Apress) and Microservices for the Enterprise (Apress). Three of his books were translated into Korean, and one is being translated into Chinese. He blogs on various topics, including blockchain, Revised Payment Services Directive, GDPR, IAM and microservices security.
https://www.manning.com/books/openid-connect-in-action?query=cybersecurity https://www.manning.com/books/openid-connect-in-action?query=cybersecurity