Skip to content

Authorization Code with PKCE Flow

The authorization code flow with PKCE is the recommended authorization flow if you’re implementing authorization in a mobile app, single page web apps, or any other type of application where the client secret can’t be safely stored.

The implementation of the PKCE extension consists of the following steps:

Pre-requisites

This guide assumes that:

Example

You can find an example app implementing Authorization Code flow with PKCE extension on GitHub in the web-api-examples repository.

Code Verifier

The PKCE authorization flow starts with the creation of a code verifier. According to the PKCE standard, a code verifier is a high-entropy cryptographic random string with a length between 43 and 128 characters (the longer the better). It can contain letters, digits, underscores, periods, hyphens, or tildes.

The code verifier could be implemented using the following JavaScript function:


_10
const generateRandomString = (length) => {
_10
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
_10
const values = crypto.getRandomValues(new Uint8Array(length));
_10
return values.reduce((acc, x) => acc + possible[x % possible.length], "");
_10
}
_10
_10
const codeVerifier = generateRandomString(64);

Code Challenge

Once the code verifier has been generated, we must transform (hash) it using the SHA256 algorithm. This is the value that will be sent within the user authorization request.

Let's use window.crypto.subtle.digest to generate the value using the SHA256 algorithm from the given data:


_10
const sha256 = async (plain) => {
_10
const encoder = new TextEncoder()
_10
const data = encoder.encode(plain)
_10
return window.crypto.subtle.digest('SHA-256', data)
_10
}

Next, we will implement a function base64encode that returns the base64 representation of the digest we just calculated with the sha256 function:


_10
const base64encode = (input) => {
_10
return btoa(String.fromCharCode(...new Uint8Array(input)))
_10
.replace(/=/g, '')
_10
.replace(/\+/g, '-')
_10
.replace(/\//g, '_');
_10
}

Let's put all the pieces together to implement the code challenge generation:


_10
const hashed = await sha256(codeVerifier)
_10
const codeChallenge = base64encode(hashed);

Request User Authorization

To request authorization from the user, a GET request must be made to the /authorize endpoint. This request should include the same parameters as the authorization code flow, along with two additional parameters: code_challenge and code_challenge_method:

Query ParameterRelevanceValue
client_idRequiredThe Client ID generated after registering your application.
response_typeRequiredSet to code.
redirect_uriRequiredThe URI to redirect to after the user grants or denies permission. This URI needs to have been entered in the Redirect URI allowlist that you specified when you registered your application (See the app guide). The value of redirect_uri here must exactly match one of the values you entered when you registered your application, including upper or lowercase, terminating slashes, and such.
stateOptional, but strongly recommendedThis provides protection against attacks such as cross-site request forgery. See RFC-6749.
scopeOptionalA space-separated list of scopes. If no scopes are specified, authorization will be granted only to access publicly available information: that is, only information normally visible in the Spotify desktop, web, and mobile players.
code_challenge_methodRequiredSet to S256.
code_challengeRequiredSet to the code challenge that your app calculated in the previous step.

The code for requesting user authorization looks as follows:


_20
const clientId = 'YOUR_CLIENT_ID';
_20
const redirectUri = 'http://localhost:8080';
_20
_20
const scope = 'user-read-private user-read-email';
_20
const authUrl = new URL("https://accounts.spotify.com/authorize")
_20
_20
// generated in the previous step
_20
window.localStorage.setItem('code_verifier', codeVerifier);
_20
_20
const params = {
_20
response_type: 'code',
_20
client_id: clientId,
_20
scope,
_20
code_challenge_method: 'S256',
_20
code_challenge: codeChallenge,
_20
redirect_uri: redirectUri,
_20
}
_20
_20
authUrl.search = new URLSearchParams(params).toString();
_20
window.location.href = authUrl.toString();

The app generates a PKCE code challenge and redirects to the Spotify authorization server login page by updating the window.location object value. This allows the user to grant permissions to our application

Please note that the code verifier value is stored locally using the localStorage JavaScript property for use in the next step of the authorization flow.

Response

If the user accepts the requested permissions, the OAuth service redirects the user back to the URL specified in the redirect_uri field. This callback contains two query parameters within the URL:

Query ParameterValue
codeAn authorization code that can be exchanged for an access token.
stateThe value of the state parameter supplied in the request.

We must then parse the URL to retrieve the code parameter:


_10
const urlParams = new URLSearchParams(window.location.search);
_10
let code = urlParams.get('code');

The code will be necessary to request the access token in the next step.

If the user does not accept your request or if an error has occurred, the response query string contains the following parameters:

Query ParameterValue
errorThe reason authorization failed, for example: "access_denied"
stateThe value of the state parameter supplied in the request.

Request an access token

After the user accepts the authorization request of the previous step, we can exchange the authorization code for an access token. We must send a POST request to the /api/token endpoint with the following parameters:

Body ParametersRelevanceValue
grant_typeRequiredThis field must contain the value authorization_code.
codeRequiredThe authorization code returned from the previous request.
redirect_uriRequiredThis parameter is used for validation only (there is no actual redirection). The value of this parameter must exactly match the value of redirect_uri supplied when requesting the authorization code.
client_idRequiredThe client ID for your app, available from the developer dashboard.
code_verifierRequiredThe value of this parameter must match the value of the code_verifier that your app generated in the previous step.

The request must include the following HTTP header:

Header ParameterRelevanceValue
Content-TypeRequiredSet to application/x-www-form-urlencoded.

The request of the token could be implemented with the following JavaScript function:


_24
const getToken = async code => {
_24
_24
// stored in the previous step
_24
let codeVerifier = localStorage.getItem('code_verifier');
_24
_24
const payload = {
_24
method: 'POST',
_24
headers: {
_24
'Content-Type': 'application/x-www-form-urlencoded',
_24
},
_24
body: new URLSearchParams({
_24
client_id: clientId,
_24
grant_type: 'authorization_code',
_24
code,
_24
redirect_uri: redirectUri,
_24
code_verifier: codeVerifier,
_24
}),
_24
}
_24
_24
const body = await fetch(url, payload);
_24
const response =await body.json();
_24
_24
localStorage.setItem('access_token', response.access_token);
_24
}

Response

On success, the response will have a 200 OK status and the following JSON data in the response body:

keyTypeDescription
access_tokenstringAn access token that can be provided in subsequent calls, for example to Spotify Web API services.
token_typestringHow the access token may be used: always "Bearer".
scopestringA space-separated list of scopes which have been granted for this access_token
expires_inintThe time period (in seconds) for which the access token is valid.
refresh_tokenstringSee refreshing tokens.

What's next?

  • Great! We have the access token. Now you might be wondering: what do I do with it? Take a look at to the access token guide to learn how to make an API call using your new fresh access token.

  • If your access token has expired, you can learn how to issue a new one without requiring users to reauthorize your application by reading the refresh token guide.