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. In this article, we will explain the OAuth2 and OpenID Connect Authorization Code flow in a way that
everyone can understand. We will also discuss different parameters such as grant_type
, redirect_uri
, prompt
, scope
,
state
and explain how to get ID tokens and refresh tokens. Additionally, we will cover 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:
- The user clicks on a link or button on a web page that requests access to a resource.
- The user is redirected to the Authorization Server, where they authenticate themselves and grant permission to the requesting application.
- The Authorization Server generates an authorization code and redirects the user back to the requesting application with the authorization code.
- 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
In the first step, 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 tocode
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. Theredirect_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 can take the following values: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.
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 & 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 & OpenID Connect being passed as a hint about the End-User's current or past authenticated session with the Client. If the End-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, typicallylogin_required
. It does not matter if the ID Token is expired or not.
Step 2: Redirect back to the app
In the second step, the user is redirected to the Authorization Server, where they authenticate themselves and grant permission to the requesting application. The Authorization Server may ask the user to login or prompt them to authorize the access request. 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 3: Exchange code for token
In the third step, 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 toauthorization_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.
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.
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.
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:
// 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 app using a 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 will use a custom redirect scheme (myapp://callback
) to receive the authorization code after the user has
granted permission. We will 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
Request consent:
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.
Single page app using a 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 will use a standard HTTPS redirect (https://myapp.com/callback
) to receive the authorization code after the
user has granted permission. We will 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
Request consent:
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 using a 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))