Skip to main content

OAuth2 authorization code flow

OAuth2 and OpenID Connect are widely used authorization and authentication delegation protocols that enable secure access to APIs and web applications. This document explains how to obtain ID tokens and refresh tokens, and also discusses various parameters, such as grant_type, redirect_uri, prompt, scope, state. Additionally, this document describes how to perform PKCE on top of the Authorization Code flow.

The Authorization Code flow is the most secure and widely used OAuth2 flow for web applications. Here is the high-level overview of the Authorization Code flow:

  1. The user clicks on a link or button on a web page that requests access to a resource.
  2. The user is redirected to the Authorization Server, where they authenticate themselves and grant permission to the requesting application.
  3. The Authorization Server generates an authorization code and redirects the user back to the requesting application with the authorization code.
  4. The requesting application exchanges the authorization code for an access token that can be used to access the protected resource.

Step 1: Get the user's permission

The user clicks on a link or button on a web page that requests access to a resource. The requesting application sends a request to the Authorization Server with the following parameters:

  • response_type: The value of this parameter should be set to code to indicate that the Authorization Code flow will be used.
  • client_id: The ID of the client that is making the request.
  • redirect_uri: The URL where the Authorization Server will redirect the user after they grant permission. The redirect_uri needs to be pre-registered with the OAuth2 client.
  • state: A random value that is generated by the requesting application to prevent cross-site request forgery (CSRF) attacks.
  • prompt (optional): This parameter takes one or more the following values (space delimited):
    • none: This value indicates that the Authorization Server should not display any user interaction pages. If the user is not already authenticated or has not already granted consent, the Authorization Server returns an error.
    • login: This value indicates that the Authorization Server should prompt the user to login before processing the access request.
    • consent: This value indicates that the Authorization Server should prompt the user to grant consent before processing the access request.
    • registration: This value indicates that the Authorization Server should display the registration UI instead of the login UI. The exact URL is governed by the urls.registration config value. It is set to /ui/registration by default.
  • scope (optional): The scope of the access request, which specifies what resources the requesting application can access.
  • max_age (optional): specifies the allowable elapsed time in seconds since the last time the End-User was actively authenticated by Ory OAuth2 and OpenID Connect. If the elapsed time is greater than this value, the Login HTML Form must be shown and the End-User must re-authenticate.
  • id_token_hint (optional): ID Token previously issued by Ory OAuth2 and OpenID Connect being passed as a hint about the user's current or past authenticated session with the Client. If the user identified by the ID Token is logged in or is logged in by the request, then the Authorization Server returns a positive response; otherwise, it returns an error, typically login_required. It does not matter if the ID Token is expired or not.

Step 2: Redirect to the Authorization Server

The user is redirected to the Authorization Server, where they authenticate themselves and grant permission to the requesting application. The Authorization Server could ask the user to log in or prompt them to authorize the access request.

Step 3: Redirect back to the app

If the user grants permission, the Authorization Server generates an authorization code and redirects the user back to the requesting application with the authorization code.

Step 4: Exchange code for token

The requesting application exchanges the authorization code for an access token that can be used to access the protected resource. The requesting application sends a POST request to the Authorization Server with the following parameters:

  • grant_type: The value of this parameter should be set to authorization_code to indicate that the authorization code will be exchanged for an access token.
  • client_id: The ID of the client that is making the request.
  • client_secret: The client secret that is used to authenticate the client.
  • code: The authorization code that was received in the previous step.
  • redirect_uri: The URL where the Authorization Server redirected the user after they granted permission.

The Authorization Server validates the request and responds with an access token and a refresh token (if enabled). The requesting application can use the access token to access the protected resource.

Modifying the authorization code flow

Get a refresh token

By default, the Authorization Code flow returns an access token that expires after a certain period of time. To get a refresh token, you need to include the offline_access scope in the access request.

note

The client needs to be allowed to request the offline_access scope and the user has to accept that the client may use the offline_access scope on the consent screen.

The offline_access scope allows the requesting application to obtain a refresh token that can be used to obtain a new access token without requiring the user to re-authenticate.

Get an OpenID Connect ID token and validate it

To obtain an ID token, you need to include the openid scope in the access request. The ID token is a JSON Web Token (JWT) that contains information about the authenticated user. The ID token can be used to obtain information about the user such as their name and email address.

note

The client needs to be allowed to request the openid scope and the user has to accept that the client may use the openid scope on the consent screen.

To validate the ID token, you need to decode the JWT and verify the signature using the public key of the Authorization Server which is available at

https://{project.slug}.projects.oryapis.com/.well-known/jwks.json

The ID token contains a signature that is used to verify that the token has not been tampered with.

Perform PKCE

PKCE (Proof Key for Code Exchange) is a security extension to the Authorization Code flow that is designed to prevent code injection attacks. In the PKCE flow, the client generates a random code verifier and transforms it into a code challenge that is sent to the Authorization Server. The Authorization Server uses the code challenge to validate the authorization code.

info

The JavaScript example code contained in this article is exemplary and explains what happens under the hood. Everyone should use tried and tested open source libraries to consume OAuth2 and OpenID Connect. Writing this code by oneself should not be done, as you would not write your own SHA512 library.

Here's a JavaScript code example that demonstrates how to generate a code verifier and code challenge using the SHA-256 hash algorithm:

PKCE example
// Generate a random code verifier with ASCII content
const codeVerifier = generateRandomASCIIString(64)

// Calculate the SHA-256 hash of the code verifier and base64-url encode it
const codeChallenge = base64UrlEncode(crypto.subtle.digest("SHA-256", new TextEncoder().encode(codeVerifier)))

const redirectUri = "..."
const clientId = "..."

// Send the code challenge along with the authorization request
const authorizationUrl = `https://{project.slug}.projects.oryapis.com/oauth2/auth?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}&code_challenge=${codeChallenge}&code_challenge_method=S256`

// Exchange the authorization code for a token using the code verifier
const code = new URLSearchParams(window.location.search).get("code")
const tokenUrl = "https://{project.slug}.projects.oryapis.com/oauth2/token"
const tokenRequestBody = new URLSearchParams({
grant_type: "authorization_code",
client_id: clientId,
// client_secret: ...
redirect_uri: redirectUri,
code_verifier: codeVerifier,
code: code,
})
fetch(tokenUrl, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: tokenRequestBody.toString(),
})
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error(error))

In this example, the generateRandomASCIIString() function is used to generate a random code verifier with a length of 64 characters. The base64UrlEncode() function is used to encode the SHA-256 hash of the code verifier as a base64-url encoded string.

The code verifier is sent along with the authorization request as a parameter, while the code challenge is generated and encoded as a parameter on the authorization URL.

When the Authorization Server sends the authorization code back to the client, the client includes the code verifier in the token request along with the code, redirect URI and other parameters. The Authorization Server then validates the code and the code challenge to ensure that the code was generated by the client that initiated the authorization request.

Examples

This section demonstrates how to use Ory OAuth2 and OpenID Connect in different types of web applications. The examples cover the use of public clients with PKCE, confidential clients, custom redirect schemes in mobile/native apps, and more.

Mobile/native and public client with PKCE and custom redirect scheme

Mobile and native applications cannot store client secrets securely, so they are typically considered public clients. In this scenario, the Authorization Code flow with PKCE can be used to provide a secure authorization and authentication mechanism.

In this example, we use a custom redirect scheme (myapp://callback) to receive the authorization code after the user has granted permission. We also generate a random code verifier and transform it into a code challenge using the SHA-256 hash algorithm.

Create OAuth2 client

ory create oauth2-client --token-endpoint-auth-method none
const clientId = "your_client_id"
const authorizationUrl = `https://auth-server.com/authorize?response_type=code&client_id=${clientId}&redirect_uri=myapp://callback&scope=openid&state=12345&code_challenge_method=S256&code_challenge=${codeChallenge}`

// Launch the system browser to start the authorization flow
Browser.openURL(authorizationUrl)

In this code, we construct the authorization URL with the required parameters, including the code_challenge_method and code_challenge for PKCE. We then launch the system browser to start the authorization flow.

Exchange code for token

const code = "authorization_code_received_from_auth_server"
const tokenUrl = "https://{project.slug}.projects.oryapis.com/oauth2/token"
const requestBody = `grant_type=authorization_code&client_id=${clientId}&code_verifier=${codeVerifier}&code=${code}`

fetch(tokenUrl, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: requestBody,
})
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error(error))

In this code, we exchange the authorization code received from the Authorization Server for an access token. We include the code_verifier parameter to validate the authorization code and prevent code injection attacks.

SPA and public client with PKCE

Single page applications (SPAs) typically use a public client model, where the client ID is publicly known. In this scenario, the Authorization Code flow with PKCE can be used to provide a secure authorization and authentication mechanism.

In this example, we use a standard HTTPS redirect (https://myapp.com/callback) to receive the authorization code after the user has granted permission. We also generate a random code verifier and transform it into a code challenge using the SHA-256 hash algorithm.

Create OAuth2 client

ory create oauth2-client --token-endpoint-auth-method none
const clientId = "your_client_id"
const authorizationUrl = `https://{project.slug}.projects.oryapis.com/oauth2/auth?response_type=code&client_id=${clientId}&redirect_uri=https://myapp.com/callback&scope=openid&state=12345&code_challenge_method=S256&code_challenge=${codeChallenge}`

// Redirect the user to the Authorization Server to start the authorization flow
window.location = authorizationUrl

In this code, we construct the authorization URL with the required parameters, including the PKCE code_challenge_method and code_challenge. We then redirect the user to the Authorization Server to start the authorization flow.

Exchange code for token

const code = "authorization_code_received_from_auth_server"
const tokenUrl = "https://{project.slug}.projects.oryapis.com/oauth2/token"
const requestBody = `grant_type=authorization_code&client_id=${clientId}&code_verifier=${codeVerifier}&code=${code}`

fetch(tokenUrl, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: requestBody,
})
.then((data) => console.log(data))
.catch((error) => console.error(error))

In this code, we exchange the authorization code received from the Authorization Server for an access token. We include the code_verifier parameter to validate the authorization code and prevent code injection attacks.

Web server app and confidential client

Web server applications can use a confidential client model, where the client ID and secret are securely stored on the server. In this scenario, the Authorization Code flow with a confidential client can be used to provide a secure authorization and authentication mechanism.

In this example, we will use a standard HTTPS redirect (https://myapp.com/callback) to receive the authorization code after the user has granted permission. We will also include the offline_access scope in the access request

Create OAuth2 client

ory create oauth2-client --token-endpoint-auth-method client_secret_post

Request Access

const clientId = "your_client_id"
const authorizationUrl = `https://{project.slug}.projects.oryapis.com/oauth2/auth?response_type=code&client_id=${clientId}&redirect_uri=https://myapp.com/callback&scope=openid%20offline_access&state=12345`

// Redirect the user to the Authorization Server to start the authorization flow
window.location = authorizationUrl

Exchange code for token

const clientSecret = "your_client_secret"
const code = "authorization_code_received_from_auth_server"
const tokenUrl = "https://{project.slug}.projects.oryapis.com/oauth2/token"
const requestBody = `grant_type=authorization_code&client_id=${clientId}&client_secret=${clientSecret}&code=${code}&redirect_uri=https://myapp.com/callback`

fetch(tokenUrl, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: requestBody,
})
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error(error))