Creating an OpenID connect system with Angular 8 and IdentityServer4 (OIDC part 1)

Share on facebook
Share on google
Share on twitter
Share on linkedin

OpenID connect authentication with dotnet core and Angular will demonstrate how to set up an app that supports authentication and access control of certain resources in the system. This guide is based on the Identity Server docs which seems to favor a setup with a client, an Identity server and an API being with authorized resources. This setup implements the OpenID connect standard which enables single sign-on and distributed access control.

OpenID Connect is a standard adding authentication (verifying the user’s identity) on top of OAUTH2, which is only for authorization (access control). OpenID connect adds authentication by introducing the notion of an ID token, which is a JWT, providing a signed proof of authentication of the user.

The OpenID connect with IdentityServer4 and Angular series

This series is learning you OpenID connect with Angular with these parts:

Why use OpenID Connect?

  1. OpenID connect is very useful for centralizing authentication and authorization in an infrastructure with many micro-services, enabling single sign-on for the user and for resource services to easily hook into the auth setup.
  2. OpenID connect is a common standard, making it easy for team members to collaborate with a widely used and well-documented standard. It also makes it very easy for new employees to understand the architecture if it’s built around a common standard.
  3. Lots of good tools are making the implementation of this standard easy, like IdentityServer.

The access token

OpenID connect uses the access token JWT from OAuth2, which is a JWT token that is used to access authorized resources. It contains information about the issuer (the authorization server), audience for whom the access token is for and a scope list, which are the scopes this token grants access to. This access token needs to be sent to the resource API on every authorized resource request and a valid access token is required for accessing authorized resources.

A JWT is encoded in base64, so it can easily be decoded for accessing reading the header and payload it is containing (so don’t store confidential information in a JWT!). The header contains metadata for the JWT like algorithm used (can be asymmetric and symmetric). The payload contains claims connected to the access grant. Lastly is the signature composed of a hash of header and payload. This ensures that if the JWT got modified by a client the signature would invalidate the JWT.

An access token can look like this:

ID token

The ID token is a JWT (JSON Web Token) containing a digitally signed proof of user authentication, asserting the users’ identity with a unique subject id (sub). It specifies the time the user was authenticated (iat) and for whom this is for (aud). The ID Token was introduced to OpenID connect for allowing the client to verify that the user has been successfully authenticated before the expiration (exp) timestamp. The ID token is never sent to the resource API and only used by the client to validate if the user is properly authenticated before requesting authorized resources.

The ID Token purpose is also to protect the user against cross-site request forgery (CSRF), man-in-the-middle (MITM) and replay attacks because the client ensures that it is logged in using a trusted authorization server (checking issuer (iss) and validating signature of id_token).

Replay attacks are avoided by checking the nonce, which is a unique random code send on authentication to the authentication end point. This ensures that if a man in the middle tries to create login by replaying the authentication request, will fail, as the nonce will already have been used.

An ID token can look like this:

The three flows of OpenID connect

In OpenID connect there are three flows, all based on the value of the response_type in the login request:

  • Authorization code with response_type: ‘code’ (authorization code that can be exchanged for tokens and refresh token in another round trip)
  • Implicit flow with response_type: ‘id_token’ and ‘id_token token’ (get the ID token or the ID token and access token)
  • Hybrid flow with response_type: ‘code id_token’, ‘code token’ and ‘code id_token token’ (always get the authorization code as well as either ID token and/or access token in first response)

Authorization code flow

This flow is the most commonly used flow used by traditional web apps with a server backend and contains two round trips:

  1. Getting authorization code.
  2. Exchange authorization code for tokens, including refresh tokens.

The flow is initiated with the response_type parameter set to code and a client secret shared between the client and the auth server in the login request. After the user has been logged in, the authorization endpoint on the authorization server sends the authorization code (using query params in a redirect), which can be exchanged for an id_token, access token and/or a refresh token. The client server then gets this authorization code and exchanges it for token(s) by sending the authorization code to the token endpoint. The client now gets the tokens and authenticates the user by validating the ID token.

Proof Key for Code Exchange (PKCE)

Because the code flow above requires a client secret to request the authorization code and a public client, like a single-page application, can’t store secrets, another approach needs to be taken here. The Proof Key for Code Exchange (PKCE) solves this by instead of using a fixed client secret generates a dynamic client secret at the beginning of the authorization request. This ensures:

  • Man in the middle attack can’t issue tokens, even if they collect the authorization code as they need the code verifier
  • No tokens are send using redirects which is less safe than doing a direct HTTP request to the authorization server (browser history and accessing tokens from XSS attack as they are available from window.location)

Notice in the sequence diagram above how this is different from the regular authorization code flow.
First, we don’t need to use the client backend anymore as we don’t store a secret. Using PKCE adds these steps to the authorization code flow:

  1.  Generate code verifier, a cryptographically random string using the characters A-Z, a-z, 0-9, and the punctuation characters -._~ (hyphen, period, underscore, and tilde), between 43 and 128 characters long
  2. Use the code verifier to generate the code challenge. This requires that the device can perform a SHA256 hash. The code challenge is a BASE64 URL encoded string of this SHA256 hash. If the client can’t perform the SHA256 hash it is permitted to use the plain code verifier string as a challenge.
  3. The code_challenge and code_challende_method are sent on the authorization request along with the usual parameters in the authorization request. The code_challenge is either the code verifier or the BASE64 encoded SHA256 hash. If it is the plain code verifier code_challenge_method is set to plain, else it is set to S256.
  4. On the access token request, the code_verifier is sent so the authorization server can verify that the client requesting the tokens are the same that did the authorization request in step 3.

This is the recommended authorization approach for public clients like native apps and SPAs so we will be looking more into this when we set up the Angular app.

Implicit flow

2019 update: Don’t use implicit flow, use PKCE instead

This flow is previously used for browser-based apps that don’t have a back end. Now, it is recommended to use code flow with PKCE instead. For historical reasons, I will keep this section even though we are not going to be working with implicit flow.

This flow is called implicit flow because the authentication is implicit from a redirect when the user has successfully logged in. After the user has logged in the authorization server returns a redirect to the client containing the access token and/or id_token. Be aware that this flow should not use refresh tokens as these are can be stolen from the browser with huge security consequences.

When working with implicit flow it is crucial to always use https so a man in the middle attack is not intercepting the access token. Also, even though the access token is typically short-lived it is stored in the app state and therefore the client should be aware of cross-site scripting (XSS) vulnerabilities if a malicious hacker gets to add javascript to the app and fetches access tokens. For these reasons, the implicit flow is perceived the most unsecure flow but the vulnerabilities can be overcome with https, XSS protection, and proper token validation according to the OpenID Connect specification. Still, it is recommended to use code flow with PKCE for public clients.

Hybrid flow

This flow contains a mix of the two above by requesting both an authorization code and tokens on first round trip. This flow enables the back end and front end to retrieve their own scoped tokens, such as a scope with refresh token for the back end and access tokens for the front end but is not used very often. The flow is initiated by requesting the authorization endpoint with the following response_type parameter values: Code id_token (returns authorization code and ID token) Code token (returns authorization code and access token) Code id_token token (returns authorization code, id_token and access token)

The setup for Angular apps

We are going to implement code flow with PKCE in our app because we are using it with an Angular app, which should not keep secrets as it runs in the browser (public client). Therefore we use PKCE to generate a dynamic secret (code_challenge) to ensure that only the client doing the authorization request can also request the access token,  thus overcoming the implicit flow security concerns, when tokens are sent as redirects (front channel).

The basic flow for Angular app authentication is:

Here are the steps in the sequence diagram for code flow with PKCE:

  1. The user is trying to navigate to an authorized resource
  2. The app is requesting the authorized resource without a valid access token
  3. The Resource server returns an error 401
  4. The 401 response makes the Angular app navigate to the Authorization endpoint on the Authorization server. Before the app navigates it generates a code verifier, to generate a code_challenge to be used in the authorization request, along with the code_challenge_method and response_code: code for requesting an authorization code.
  5. The authorization server stores the code_challenge and code_challenge_method and redirects to the login page
  6. The user logs in and gives consent to accessing the authorized resources
  7. Authorization endpoint returns redirect with the authorization code
  8. The Angular app performs the access token request by sending the authorization code and the code verifier to the authorization server
  9. The authorization server verifies that the code_verifier matches the stored code_challenge using the stored code_challenge_method.
  10. The Angular app validates the returned tokens from the authorization server. It validates the id_token and access token according to http://openid.net/specs/openid-connect-implicit-1_0.html#IDTokenValidation
  11. Angular app requests authorized resource with the newly obtained access token.
  12. The resource server is getting the json web key set (JWKS) from the authentication server (at {authUrl}/.well-known/openid-configuration/jwks) for verifying the access token signature with the public key, matching the private key that signed the JWT. Future requests will just use an in-memory cache of this JWKS. The claims of the access token are hereafter validated according to this.
  13. The resource server now returns authorized resource to the client

In the following parts we are going to create an OpenID Connect code flow with PKCE implementation with:

  • Client server, hosting an Angular app
  • Authorization server implemented as an ASP.NET core Identity server
  • Resource server implemented as an ASP.NET core web API

Next part we will look into how to set up the authorization server with identity server 4.

Resources

https://connect2id.com/learn/openid-connect

Do you want to become an Angular architect? Check out Angular Architect Accelerator.

Related Posts and Comments

How I migrated my Course Platform to Analog (step by step)

Analog is a full-stack framework for Angular ala NextJS that offers server-side rendering, static site generation, and API routes. Analog empowers Angular with server rendering tools making it optimal for public websites. Otherwise, Angular has often been neglected in favor of NextJS/NuxtJS for these purposes (due to the subpar SSR/SSG experience). I recently migrated my

Read More »

The Future of Angular and the Latest Features in Angular

In this video, I’ll be discussing the future of Angular and the latest features in Angular 17, including standalone components, signals, new control flow, the deferred operator, and server-side rendering improvements. I’ll also touch on the use of Tailwind CSS for styling and the benefits it offers. Join me to learn more about the exciting

Read More »

How to Invest as a Software Developer

In this video, I share my personal investment strategy as a software developer, focusing on putting money to work and owning assets that generate cash flow and appreciation. I discuss the snowball effect of building an investment portfolio over time and the importance of compounding. I also touch on the allocation of investments in stocks,

Read More »

Angular 17: What’s new?

Angular has since the latest major version started what the Angular team calls a renaissance, which means a radical renewal of the framework. The main goals of the latest updates have been to improve the developer experience and performance so it aligns more with the other leading front-end frameworks in the space by introducing new

Read More »

How to Set up a CI pipeline with Azure Pipelines and Nx

It goes without saying that having a CI pipeline for your Angular apps is a must. Setting one up for regular Angular apps is fairly straightforward but when you have an Nx monorepo there are certain other challenges that you have to overcome to successfully orchestrate a “build once, deploy many” pipeline. This post will

Read More »