OAuth 2.0 Authorization Code Grant: What it is & how it works
A comprehensive guide to the OAuth 2.0 Authorization Code Grant, including how the flow works, how to implement it with PKCE, and what’s new in OAuth 2.1.
OAuth 2.0 is the industry-standard protocol for authorization. It enables applications to securely access user data without handling passwords directly. Among its several grant types, the Authorization Code Grant is the most robust and widely adopted for web applications.
In this article, we’ll break down how the Authorization Code Grant works, when to use it, and how to implement it securely.
What is the Authorization Code Grant?
The Authorization Code Grant is a two-step OAuth 2.0 flow designed for applications that can securely store a client secret (like web apps running on a backend server). It’s the go-to choice when you want:
- A high level of security.
- Access tokens delivered via a backend server (not exposed to the browser).
- The ability to request refresh tokens.
How the Authorization Code Grant works
The Authorization Code Grant flow in OAuth 2.0 involves three main stages: requesting user consent, handling the redirect with an authorization code, and exchanging that code for tokens. This flow requires a trusted authorization server to coordinate each step securely.
You can build and host your own authorization server, but most modern applications use a third-party OAuth provider, like WorkOS, which offers a fully managed and OAuth 2.1–compliant implementation out of the box.
Let’s walk through how this flow works in practice.
1. Get the user’s permission via /authorize
To initiate the flow, the application constructs a URL to the authorization server’s /authorize
endpoint, then opens it in the user's browser. This begins the login and consent process.
The URL looks like this:
Parameter breakdown:
response_type
=code: Specifies that the application is initiating the Authorization Code Grant flow.
client_id
: A public identifier for the app, issued when the developer registered the application with the authorization server. Think of it as your app’s username.
redirect_uri
: The URI to which the authorization server will send the user after approval (i.e., where in your app do you want users to land after they authenticate). This must match the URI registered with the app to prevent redirect attacks.
scope
: A space-separated list of permissions the app is requesting (e.g., read write delete). The specific scopes vary based on the API you're working with. An example of scopes is you logging into an app with Google and Google asking you to verify if you want to disclose to the app your email address and name. In this case the email address and name are the scoped asked by the app during this step.
state
: A randomly generated string that the application should store temporarily. When the user returns, the app compares this value to prevent Cross-Site Request Forgery (CSRF) attacks.
Once the user visits this URL, the authorization server displays a prompt requesting their consent to the scopes the application has requested.
2. Redirect with authorization code
If the user approves the request, the authorization server redirects the browser to the redirect_uri, appending both the authorization code and the original state value as query parameters.
Example redirect URL:
Parameter breakdown:
code
: A short-lived, one-time-use authorization code. It usually expires within 1–10 minutes.
state
: The same random string originally sent in the authorization request. Your application must validate it to ensure the integrity of the flow.
At this point, the application has not yet received any tokens. The authorization code is only a temporary credential to be exchanged securely.
3. Exchanging the code for an access token via /token
The final step is to exchange the authorization code for an access token by making a secure POST request to the token endpoint (/token
).
Example POST request:
Parameter breakdown:
grant_type=authorization_code
: Indicates the use of the Authorization Code Grant.code
: The authorization code received in the previous redirect.redirect_uri
: Must match the one used in the initial/authorize
request.
client_id
andclient_secret
: Credentials that authenticate the application itself. These must be stored securely on the backend. Think of them as the username and password of your app.
The authorization server validates all parameters, especially the code, redirect_uri, and credentials. If the request is valid, it responds with an access token, and optionally a refresh token and ID token.
Example token response:
Your application now has what it needs to authenticate API requests on behalf of the user.
!!For detailed steps on how to implement this flow using WorkOS, see our docs.!!
Next up, we'll explore how PKCE secures this flow further, especially now that it's required in OAuth 2.1.
Securing the flow with PKCE (mandatory in OAuth 2.1)
Originally introduced as an extension to OAuth 2.0 for public clients like mobile apps, PKCE (Proof Key for Code Exchange) is now a required part of the Authorization Code flow in OAuth 2.1.
This is a significant shift: even server-side apps (confidential clients) must now use PKCE to protect against authorization code interception attacks.
Without PKCE, an attacker who intercepts an authorization code (e.g., via an open redirect or malicious client) might be able to exchange it for an access token if they know the client ID and redirect URI.
PKCE prevents this by binding the authorization request and token exchange using a two new parameters: the code_challenge and code_verifier.
How PKCE works
- The client generates a code verifier. This is a high-entropy cryptographically random string (43–128 characters).
- The client derives a code challenge from the verifier using SHA-256 and Base64URL encoding.
- The code challenge is sent to /authorize alongside the other parameters. The authorization server stores the code challenge and proceeds with user authentication.
- Later, during the /token request, the client sends the original code_verifier.
- The authorization server hashes the code_verifier and compares it with the original code_challenge. If they match, the token is issued.
.webp)
This binding ensures that only the app that initiated the flow (and knows the code verifier) can complete the exchange. even if an attacker intercepts the authorization code. It eliminates the possibility of replay attacks.
For more details on this flow, see Secure authentication for frontend apps with PKCE.
How to generate a code challenge (Node.js)
Store the codeVerifier
temporarily (e.g., in session storage or backend memory). You’ll need it during the token exchange step.
Common pitfalls
- Don’t skip PKCE, even on the server-side, OAuth 2.1 makes this non-negotiable.
- Always verify state.
- Enforce exact match of redirect_uri.
- Never expose client secrets in client-side code.
- Use HTTPS for all endpoints (OAuth 2.1 requirement).
Example: Node.js + PKCE
Authorization Code Grant FAQ
What are the two main endpoints in OAuth’s Authorization Code Grant?
They are /authorize
(initiates the flow via browser) and /token (backend exchange for tokens).
Why is PKCE mandatory in OAuth 2.1?
It protects against code interception by requiring a dynamic proof (code_verifier+challenge) on every token exchange.
Can confidential (server-side) apps skip PKCE since they securely store the secret?
No. PKCE is now mandatory across all clients—even confidential ones—to bolster security.
Can I use Authorization Code Grant for mobile or single-page applications?
Yes, but only with PKCE. While mobile and SPA clients can’t store client secrets securely, PKCE ensures the Authorization Code Grant is still safe by preventing code interception attacks. OAuth 2.1 mandates PKCE for all clients, so it's the preferred approach even in these environments.
Do I need to use refresh tokens with the Authorization Code Grant?
You don’t have to, but it’s often recommended. Refresh tokens allow your app to obtain new access tokens without requiring the user to log in again. This is especially useful for long-lived sessions. Keep in mind that not all authorization servers issue refresh tokens by default, you may need to request the offline_access scope.
What’s the difference between an access token, ID token, and refresh token?
- Access token: Used to access protected APIs. Short-lived.
- ID token: Contains user identity info. Used primarily in OpenID Connect flows.
- Refresh tokens: Used to get new access tokens without user interaction.
Each token serves a different role, and it’s common to receive all three in an Authorization Code flow that includes OpenID scopes.
Conclusion
The OAuth 2.0 Authorization Code Grant, when implemented correctly, is the gold standard for secure delegated access.
With clearly defined /authorize
and /token
endpoints, combined with the mandatory PKCE extension from OAuth 2.1, developers can implement robust OAuth flows across web, mobile, and desktop applications.