profile for accessing the user's profile information, such as their name
offline_access to request a Refresh Token as part of this call
Redirect URI for the callback location within the client application when sign-in is complete
A random alphanumeric string for preventing Cross-Site Request Forgery (CSRF)
The client application making an authorization code request to the authorization server, Okta, with the information mentioned above
The authorization server redirects the user, Sunny, to a web page it hosts to sign in and provide authorization consent.
The client application redirects to Okta hosted sign-in screen, and Sunny authenticates and grant consent
Yay! Sign-in success! The authorization server returns an authorization code to the client application.
The authorization server redirects back to the client application and returns an authorization code
Now that there's an authorization code, the client application makes a request to the token endpoint of the authorization server and sends the authorization code and the code verifier.
The client making a token request and sending the information listed above in the payload
To process the token endpoint request the authorization server ensures the authorization code is still valid and that the code verifier matches the code challenge sent in the authorization request. This step is part of the PKCE extension.
The authorization server verifying the authorization code and the code verifier
Yay! Success! The authorization server returns three tokens:
- Access token
- ID token
- Refresh token
The authorization server responds to the token request with three tokens
Now that Sunny successfully signed in, they can continue their rawring lessons!
A happy dinosaur getting ready to start a lesson in how to rawr
Now that you have these three tokens let's better understand what each one is for.
Auth tokens in OAuth 2.0 + OIDC
These three tokens provide crucial information about your identity, access to resources, and the ability to stay authenticated securely.
ID token
The ID token is about the user, so information about Sunny in this case. This authentication token is returned from the OpenID Connect layer. It contains standardized identity information such as email, name, and issuing party, called claims. The ID token may include extra claims for information critical to Sunny's identity, such as their dinosaur family, which is a Smileasaur of course.
Access token
The access token is a key that grants access to data or to perform an action. This authorization token is returned from the OAuth layer. The token has metadata about the token itself, such as the issuing party, information about requested scopes made in the original request, and the expiration time. Access tokens are intentionally short-lived for public clients and are a safety mechanism since it guards access to resources, and it's dangerous if it falls into the wrong hands!
Refresh token
The Refresh token allows us to exchange it for new, shiny tokens. This optional offline_access scope we added in the original authorization code request allows the access token to be short-lived but does not require Sunny to authenticate to get a new token repeatedly. Refresh tokens can be longer-lived than access tokens, but for public clients, the lifetime of Refresh tokens should also be short. We want to make sure bad actors can't get a hold of a Refresh token to gain access to an access token! For public clients like SPA, it's a best practice also to use Refresh Token rotation, which improves security by rotating refresh tokens after each use.
While each step of this OAuth flow to get the tokens is critical to ensure a secure authentication and authorization process, let's inspect the two requests in more detail.
Create an OAuth 2.0 + OIDC Compliant Authorization server
We'll start by setting up an authorization server in Okta and use the OpenID Connect Debugger tool to inspect the Network requests. This authorization server is OAuth 2.0 and OIDC compliant so we can use it within applications that conform to those specs.
Before you begin, you’ll need a free Okta developer account. Install the Okta CLI and run okta register to sign up for a new account. If you already have an account, run okta login. Then, run okta apps create. Select the default app name, or change it as you see fit. Choose Single-Page App and press Enter.
Use https://oidcdebugger.com/debug for the Redirect URI and set the Logout Redirect URI to https://oidcdebugger.com/debug.
What does the Okta CLI do?
The Okta CLI will create an OIDC Single-Page App in your Okta Org. It will add the redirect URIs you specified and grant access to the Everyone group. It will also add a trusted origin for https://oidcdebugger.com/debug. You will see output like the following when it’s finished:
Okta application configuration:
Issuer: https://dev-133337.okta.com/oauth2/default
Client ID: 0oab8eb55Kb9jdMIr5d6
NOTE: You can also use the Okta Admin Console to create your app. See Create a Single-Page App for more information.
Make a note of the Issuer and Client ID. In the upcoming steps, you'll need those values to configure Okta in your SPA client.
View the well-known endpoint for OIDC Discovery
Authorization servers have standard, public endpoints for discovery by clients. Let's take a peek at the OIDC discovery document.
The OpenID Connect specification requires a standardized mechanism for client discovery. You can find it at {Issuer}/.well-known/openid-configuration. Open up a browser tab to the OIDC discovery endpoint. It is also JSON formatted data that looks something like this:
Example OIDC well-known discovery document
In the discovery response, we have the endpoints for the authorization and token requests which we'll use in the following steps. We also have a user info endpoint to query for user information and endpoints to validate the ID token. If you find the property for claims_supported, you'll see the claims cover various identifying information about the user.
Debug the authorization code and token requests
We can see this OAuth flow using the OpenID Connect Debugger tool. Open the site on a browser with good developer debugging capabilities. You'll see something like this:
OIDC debugger site showing form fields
Some fields are pre-populated, which is helpful. Add the other key information needed:
-
Authorize URI (required) - the authorize endpoint from the discovery doc
-
Client ID (required) - the Client ID value from Okta CLI
-
Scope (required) -
openid is already added for us. Add profile and offline_access with spaces in between. The form field should look like openid profile offline_access.
-
Response type (required) - select "Use PKCE?" to unlock a few new fields, but everything is auto-populated. The debugger discovers the token endpoint automatically for us. 😎
Open debugging tools in your browser to watch for redirection and network requests. You'll want to make sure you're preserving logs between page refreshes.
At the bottom of the page, you'll see how the authorization code request is formed. Press Send Request to start the flow.
First, you'll authenticate using Okta if you still need to sign in by redirecting to an Okta-hosted sign-in page. Redirecting to the Identity Provider's hosted sign-in page is the best practice for security, so it's also a common practice you'll see across Identity Providers. Completing sign-in redirects you back to the OIDC debugger with a success message. You'll see the authorization code automatically exchanged for tokens.
Authorization code and tokens returned in the OIDC debugger upon successful sign-in
The token format for ID tokens is JSON Web Token (JWT). The access token from Okta is also a JWT. JWT is an open standard (RFC 7519) that allows systems to exchange information in JSON format securely. They are compact and safe from modifications, but our tokens contain public information. Don't worry! They are secure, just not confidential.
You can copy the access token or ID token value from the response and read the contents using a JWT debugger.
JWT inspection in the JWT debugger
So this is cool, but what do we do with the tokens in our SPA? Let's bring these concepts into our front-end application.
Add authentication to your SPA
First, we need an Okta OIDC application supporting our SPA's redirect URI. We could edit the existing OIDC debugger application we created previously, or we can use a handy Okta CLI command to set everything up for us and to scaffold out a sample application in our preferred framework.
Use the following command in the terminal to get going quickly. There's a separate command for each Angular, React, and Vue.
Angular
okta start angular
React
okta start react
Vue
okta start vue
Follow the instructions in the terminal to start the application. You should be able to sign in and sign out. Additionally, you can watch network requests for the calls it makes.
We don't have a refresh token by default. You can enable that in the Okta Admin Console. Navigate to the Okta Developer site and sign in to your Okta organization. In the Admin Console, navigate to Applications > Applications and select the Okta application for the SPA you created. Edit the settings to enable Refresh Token. It automatically adds Refresh Token Rotation. Save your changes.
If you still need to, open the SPA code in your favorite IDE. Depending on your framework, navigate to the config.js or app.config.ts file. You'll see the OIDC config and property for scopes where you will add offline_access. Your OIDC config looks something like this:
{clientId:CLIENT_ID,issuer:ISSUER,redirectUri:window.location.origin+'/login/callback',scopes:['openid','profile','email','offline_access']}
Now try rerunning the SPA. If you inspect the network request, you'll see the refresh token too. You can see the tokens by looking at the contents of your local storage too.
Use auth tokens in SPAs
Now that we have these tokens in our SPA, what do we do with them? Let a trusted OIDC library, such as the Okta SDKs, handle all the token requests and refresh them for us. Behind the scenes, the OIDC library is hard at work exchanging tokens.
Refresh token call to the authorization server and returning new tokens
We're primarily interested in the contents of the ID token and using the access token.
How to use the access token
The access token gives us access to resources. That means we use access tokens in outgoing API requests that our application needs, like for Sunny to get their next rawring lesson.
Sunny starting up a new lesson makes a call to the Rawr site's /lesson endpoint passing in the access token
You'll send the access token as a Bearer token in the HTTP call's Authorization header. So in the case of Sunny, the outgoing HTTP call looks like this.
GET /lessons
Authorization: Bearer access_token_value
You'll want to ensure you're not adding the access token to calls outside your system by maintaining an allowlist of origins that should include the token.
A great way to manage adding the Authorization header and the logic to verify the call matches the allowlist of origins is using an interceptor. Interceptors sit between your application and outgoing (or incoming) HTTP requests. Angular includes the concept of interceptors within the framework, and if you use Axios in Vue or React apps, you can configure interceptors there.
You can see access tokens in action within the SPA you previously created. If you navigate to Messages in the application, it will make a call to a resource server and add the access token to the Authorization header. If you want to see the messages request succeed, you'll need to run the resource server in Okta's samples-nodejs-express-4 repo.
Express 4 samples. Will publish an artifact that can be consumed by end-to-end sample repos
Express Sample Applications for Okta
This repository contains several sample applications that show you how to integrate various Okta use-cases into your Node.js application that uses the Express framework.
Configuration
All of the samples share a single configuration file, config.js. The config uses environment variables which can be either exported in the shell or stored in a file named testenv in this directory. See dotenv for more details on this file format. It may look something like:
ISSUER=https://yourOktaDomain.com/oauth2/default
# Web app
CLIENT_ID=123XX
CLIENT_SECRET=456XX
# SPA app
SPA_CLIENT_ID=123YY
Please find the sample that fits your use-case from the table below.
| Sample |
Description |
Use-Case |
| Okta-Hosted Login |
An application server that uses the hosted login page on your Okta org, then creates a cookie session for the user in the Express application. |
Traditional web applications with server-side rendered pages. |
| Custom Login Page |
An application server that uses the |
...
How to use the ID token
Since the ID token contains user identity information, you can use it to start populating your user store and for supporting identifiers you need immediately, such as their name.
Sunny looking at his profile in the Rawr app where they see their name, dinosaur family, and their avatar
In the SPA you previously created, you can see all your claims if you navigate to Profile.
The Okta SDK automatically decodes the ID token so that we can use the claims without decoding the payload ourselves. But before we jump right into using the claims, the Okta SDK first validates the token signature, which helps ensure the token's integrity and that it hasn't been altered. Let's take a quick peek under the covers at what happens during this validation process.
Since this token is a JWT, we can rely on standard validation for JWTs for the validity of the token itself. Next, it will validate some claims in the payload, including:
-
iss - the issuer, which should match the original request. In the OIDC Debugger tool, we passed this in as the "Authorize URI"
-
aud - the audience, which should match the client ID of your Okta application
-
nonce - an arbitrary one-time value that should match the original request. In the OIDC Debugger tool, it was pre-populated for us.
-
exp and iat - the expiry time and issued at times, respectively. Only non-expired tokens with an expected issue time here, please!
Once you have the access token, you can also make a request to the OIDC Discovery document's User Info endpoint, which may contain more information about the user than what is available in your ID token. You may also need to add any user information and user settings from API calls within your system into your user store since the SPAs we write have complex user information.
Learn more about OAuth, OIDC, tokens, and authentication best practices
Hopefully, this gives insight into how authentication and authorization with OAuth 2.0 + OpenID Connect work for public clients like SPAs and how each token fits into the landscape.
If you liked this post, you might want to check out the following:
Remember to follow us on Twitter and subscribe to our YouTube channel for more exciting content. We also want to hear about what tutorials you want to see. Leave us a comment below.