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:
- Code Challenge generation from a Code Verifier.
- Request authorization from the user and retrieve the authorization code.
- Request an access token from the authorization code.
- Finally, use the access token to make API calls.
Pre-requisites
This guide assumes that:
- You have read the authorization guide.
- You have created an app following the apps guide.
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:
_10const 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_10const 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:
_10const 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:
_10const 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:
_10const hashed = await sha256(codeVerifier)_10const 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 Parameter | Relevance | Value |
---|---|---|
client_id | Required | The Client ID generated after registering your application. |
response_type | Required | Set to code . |
redirect_uri | Required | The 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. |
state | Optional, but strongly recommended | This provides protection against attacks such as cross-site request forgery. See RFC-6749. |
scope | Optional | A 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_method | Required | Set to S256 . |
code_challenge | Required | Set to the code challenge that your app calculated in the previous step. |
The code for requesting user authorization looks as follows:
_20const clientId = 'YOUR_CLIENT_ID';_20const redirectUri = 'http://localhost:8080';_20_20const scope = 'user-read-private user-read-email';_20const authUrl = new URL("https://accounts.spotify.com/authorize")_20_20// generated in the previous step_20window.localStorage.setItem('code_verifier', codeVerifier);_20_20const 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_20authUrl.search = new URLSearchParams(params).toString();_20window.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 Parameter | Value |
---|---|
code | An authorization code that can be exchanged for an access token. |
state | The value of the state parameter supplied in the request. |
We must then parse the URL to retrieve the code
parameter:
_10const urlParams = new URLSearchParams(window.location.search);_10let 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 Parameter | Value |
---|---|
error | The reason authorization failed, for example: "access_denied" |
state | The 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 Parameters | Relevance | Value |
---|---|---|
grant_type | Required | This field must contain the value authorization_code . |
code | Required | The authorization code returned from the previous request. |
redirect_uri | Required | This 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_id | Required | The client ID for your app, available from the developer dashboard. |
code_verifier | Required | The 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 Parameter | Relevance | Value |
---|---|---|
Content-Type | Required | Set to application/x-www-form-urlencoded . |
The request of the token could be implemented with the following JavaScript function:
_24const 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:
key | Type | Description |
---|---|---|
access_token | string | An access token that can be provided in subsequent calls, for example to Spotify Web API services. |
token_type | string | How the access token may be used: always "Bearer". |
scope | string | A space-separated list of scopes which have been granted for this access_token |
expires_in | int | The time period (in seconds) for which the access token is valid. |
refresh_token | string | See 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.