Skip to content

Authorization Code Grant (w/ PKCE)

A temporary code that the client will exchange for an access token. The user authorizes the application, they are redirected back to the application with a temporary code in the URL. The application exchanges that code for the access token.

Flow

Part One

The client redirects the user to the /authorize with the following query parameters:

  • response_type must be set to code
  • client_id is the client identifier you received when you first created the application
  • redirect_uri indicates the URL to return the user to after authorization is complete, such as org.example.app://redirect
  • state is a random string generated by your application, which you’ll verify later
  • code_challenge must match the The code challenge as generated below,
  • code_challenge_method – Either plain or S256, depending on whether the challenge is the plain verifier string or the SHA256 hash of the string. If this parameter is omitted, the server will assume plain.

TIP

The client secret should never be used during the Part One of the authorization_code flow.

View sample authorization_code (part 1) request
http
GET /authorize HTTP/1.1
Host: example.com

response_type=code
&client_id=xxxxxxx
&redirect_uri=http://localhost
&scope="contacts.read contacts.write"
&state=abcdefghijklmnopqrstuvwxyz123456789
&code_challenge=92d3b56942866d1edf02c33339b7c3dc37c6201282bb238cb47f0d3289f28a93f1bdd8af6ca9913aed0c4c
&code_challenge_method=S256

The user will be asked to login to the authorization server and approve the client and requested scopes.

If the user approves the client, they will be redirected from the authorization server to the provided redirect_uri with the following fields in the query string:

  • code is the authorization code that will soon be exchanged for a token
  • state is the random string provided and should be compared against the initially provided state
View sample authorization_code (part 1) response
http
HTTP/1.1 302 Found
Location: http://localhost&code=eyJhbGciOiJIUzI1NiJ9.eyJjbGllbnRfaWQiOiJhdXRoY29kZWNsaWVudCIsInJlZGlyZWN0X3VyaSI6Imh0dHA6Ly9sb2NhbGhvc3QiLCJhdXRoX2NvZGVfaWQiOiJteS1zdXBlci1zZWNyZXQtYXV0aC1jb2RlIiwic2NvcGVzIjpbXSwiZXhwaXJlX3RpbWUiOjE2MDE3NTM3MzMsImNvZGVfY2hhbGxlbmdlIjoiT0RRd1pHTTRZelpsTnpNeU1qUXlaREF4WWpFNU1XWmtZMlJrTmpKbU1UbGxNbUkwTnpJMFpEbGtNR0psWWpGbE1tTXhPV1kyWkRJMVpEZGpNak13WWciLCJjb2RlX2NoYWxsZW5nZV9tZXRob2QiOiJTMjU2In0.OIEtZN5BHNaB4Mz0plUpGAP93EHyoil2smJiG3S_2BM&state=abcdefghijklmnopqrstuvwxyz123456789

Part Two

The client sends a POST to the /token endpoint with the following body:

  • grant_type must be set to authorization_code
  • client_id is the client identifier you received when you first created the application
  • client_secret (optional) is the client secret and should only be provided if the client is confidential
  • redirect_uri
  • code_verifier
  • code is the authorization code from the query string

Private Key Leak Potential

Clients such as Browser Based Apps and Native Mobile Apps should NEVER have or use a client_secret. That means the client_secret should be omitted both when initially creating the OAuthClient entity, and when making requests.

View sample authorization_code (part 2) request
http
POST /token HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&client_id=xxxxxxxxxx
&client_secret=xxxxxxxxxx
&redirect_uri=http://localhost
&code_verifier=OTJkM2I1Njk0Mjg2NmQxZWRmMDJjMzMzMzliN2MzZGMzN2M2MjAxMjgyYmIyMzhjYjQ3ZjBkMzI4OWYyOGE5M2YxYmRkOGFmNmNhOTkxM2FlZDBjNGM
&code=eyJhbGciOiJIUzI1NiJ9.eyJjbGllbnRfaWQiOiJhdXRoY29kZWNsaWVudCIsInJlZGlyZWN0X3VyaSI6Imh0dHA6Ly9sb2NhbGhvc3QiLCJhdXRoX2NvZGVfaWQiOiJteS1zdXBlci1zZWNyZXQtYXV0aC1jb2RlIiwic2NvcGVzIjpbXSwiZXhwaXJlX3RpbWUiOjE2MDE3NTM3MzMsImNvZGVfY2hhbGxlbmdlIjoiT0RRd1pHTTRZelpsTnpNeU1qUXlaREF4WWpFNU1XWmtZMlJrTmpKbU1UbGxNbUkwTnpJMFpEbGtNR0psWWpGbE1tTXhPV1kyWkRJMVpEZGpNak13WWciLCJjb2RlX2NoYWxsZW5nZV9tZXRob2QiOiJTMjU2In0.OIEtZN5BHNaB4Mz0plUpGAP93EHyoil2smJiG3S_2BM

The authorization server will respond with the following response

  • token_type will always be Bearer
  • expires_in is the time the token will live in seconds
  • access_token is a JWT signed token and is used to authenticate into the resource server
  • refresh_token is a JWT signed token and can be used in with the refresh grant
  • scope is a space delimited list of scopes the token has access to
View sample authorization_code (part 2) response
http
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  token_type: 'Bearer',
  expires_in: 3600,
  access_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDE3NTUxMDQsIm5iZiI6MTYwMTc1MTUwNCwiaWF0IjoxNjAxNzUxNTA0LCJqdGkiOiJuZXcgdG9rZW4iLCJjaWQiOiJ0ZXN0IGF1dGggY29kZSBjbGllbnQiLCJzY29wZSI6IiJ9.-V9x03iz-3ISRMdj9m1-FCKjmtfjvv6wqnBj6VZdW28',
  refresh_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRfaWQiOiJhdXRoY29kZWNsaWVudCIsImFjY2Vzc190b2tlbl9pZCI6Im5ldyB0b2tlbiIsInJlZnJlc2hfdG9rZW5faWQiOiJ0aGlzLWlzLW15LXN1cGVyLXNlY3JldC1yZWZyZXNoLXRva2VuIiwic2NvcGUiOiIiLCJleHBpcmVfdGltZSI6MTYwMTc1NTEwNCwiaWF0IjoxNjAxNzUxNTAzfQ.J_RUFD5-158atTmI98R95vowZWi4mUEXYCO7iNwzpK4',
  scope: 'contacts.read contacts.write'
}

PKCE

PKCE (RFC 7636) is an extension to the Authorization Code flow to prevent several attacks and to be able to securely perform the OAuth exchange from public clients.

By default, PKCE is enabled and encouraged for all users. If you need to support a legacy client system without PKCE, you can disable PKCE with the authorization server using the requiresPKCE configuration option.

Code Verifier

The code_verifier is part of the extended “PKCE” and helps mitigate the threat of having authorization codes intercepted.

Before initializing Part One of the authorization code flow, the client first creats a code_verifier. This is 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.

We can do this in Node using the native crypto package and a base64urlencode function:

typescript
import crypto from "node:crypto";

const code_verifier = crypto.randomBytes(43).toString("hex");

@see https://www.oauth.com/oauth2-servers/pkce/authorization-request/

Code Challenge

Now we need to create a code_challenge from our code_verifier.

For devices that can perform a SHA256 hash, the code challenge is a BASE64-URL-encoded string of the SHA256 hash of the code verifier.

typescript
const code_challenge = base64urlencode(
  crypto.createHash("sha256")
    .update(code_verifier)
    .digest()
);

Clients that do not have the ability to perform a SHA256 hash are permitted to use the plain code_verifier string as the code_challenge.

typescript
const code_challenge = code_verifier;
Need a base64urlencode function?
typescript
function base64urlencode(str: string) {
  return Buffer.from(str)
    .toString("base64")
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=/g, "");
}

Revocation

Authorization codes are only valid for a single use. In addition, they can be explicitly revoked on a server that supports RFC7009 “OAuth 2.0 Token Revocation”.

An authorization code revocation request will include the following parameters:

  • token is the authorization code previously issued to the client
  • token_type_hint (optional) should be set to authorization_code
View sample revoke authorization_code request
http
POST /token HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded

token_type_hint=authorization_code
&refresh_token=xxxxxxxxx

The authorization server will respond with the following response

View sample revoke authorization_code response
http
HTTP/1.1 200 OK
Cache-Control: no-store
Pragma: no-cache

Released under the MIT License.