Understanding PKCE - Part 2 - How Access token and Refresh token are generated

Shreyas skShreyas sk
5 min read

In the previous section, we got to know how to get the authorization code from Auth endpoint of server by sending client id and code_challenge.
Once this is done, our next thing is to use this client id, code and code verifier to generate access token, which will be used to authenticate the user.

In this section, we'll see

  • How to generate access token and refresh token from the authorization code.

  • How to generate new access token using refresh token if access token is expired.

  • What to do when both access token and refresh token are expired.

Generating access token:

In the previous section, we got to know that in the redirect_uri generated from authorization endpoint will have auth code and state in its params.

 https://localhost:8080/callback/?code=authzCode&state=uuid

We have to first extract the code from this url.

const url = "https://localhost:8080/callback/?code=authzCode&state=uuid";
const queryParams = url.split("?")[1];
const code = queryParams.split("&").filter((a) => a.includes("code"))[0].split("=")[1];

Once we get the code, we have to construct the payload for access token call.

client_id: "client id"
grant_type: "authorization_code"
scope: "openid offline_access eq1"
redirect_uri: "https://localhost:8080/callback"
code_verifier: (code_verifier stored while making auth endpoint call)
code: code

client_id: This is the unique identifier for token call.

grant_type: This is the parameter that determines what type of access token generation call is it. For the flow where we get access token using auth code, we use "authorization_code".

scope: This is the list of permissions allowed while making the token call. These scopes are stored in string separated by " ".

redirect_uri: This is the url where the control goes once the access token generation is successful.

code_verifier: This is the random id generated while making the authorization call.
we can store this in local storage or session storage before encoding to get code challenge.

code: This is the code that is obtained from the authorization endpoint call.

Once the payload is obtained, we can make post call to token endpoint.

fetch("tokenEndpoint", {
    method: "POST",
    body: payload,
  });

On making the call, we'll get the access_token and refresh_token in response.

{
  "access_token": {accessToken},
  "token_type":"bearer",
  "expires_in":3600,
  "refresh_token": {refresh_token}
}

access_token: This is used for authenticating the user for endpoints. This will most probably be JWT token.

refresh_token: This is the token which is used to get a new access_token when the existing access_token gets expired.

The access_token and refresh_token can be stored in cookie or local storage to for further use.

Access token expiry:

Once the access token is expired, the API will start giving "403" unauthorised error.
We have to create new access_token once the existing access_token is expired.

To determine the expiry of access_token, we use library called jwt-decode

const isAccessTokenExpired = (accessToken: string) => {
const decodedToken: { exp: number } = JwtDecode(accessToken);
return new Date() > new Date(decodedToken.exp * 1000);
}

Before making API calls that use access_token, we have to check for it's expiry as mentioned above. If this returns false, we can go ahead and send the access_token in the API headers.

If the access token is expired, new access_token has to be generated using the refresh_token obtained from token endpoint call.

Refresh Token flow:

APIs will return 403 error if calls are made with expired access_tokens.
As a fool proof mechanism, we will check for token expiry before making API call.
If the token is expired, we have to make a call to token endpoint with refresh_token.

First, we have to prepare payload for the token request with refresh token.

client_id: "client-id"
grant_type: "refresh_token"
scope: scope.join(" ")
redirect_uri: "https://localhost:8080/callback"
refresh_token: "refresh_token"

client_id: This is the unique identifier for token call.

grant_type: This is the parameter that determines what type of access token generation call is it. For refresh token flow, we use "refresh_token" as grant_type

scope: This is the list of permissions allowed while making the token call. These scopes are stored in string separated by " ".

redirect_uri: This is the url where the control goes once the access token generation is successful.

refresh_token: This is the token which is stored along with access_token which is used to create new token once the previous one expires.

We have to make the same call to token endpoint and the response will also be same.

fetch("tokenEndpoint", {
    method: "POST",
    body: payload,
  });
{
  "access_token": {accessToken},
  "token_type":"bearer",
  "expires_in":3600,
  "refresh_token": {refresh_token}
}

Once we get the new access_token, we can store it in cookie or local storage, and start using in making API calls.

What will happen if both access_token and refresh_token expire?

Even if refresh_token has a longer life still it will have a expiration same as access_token.

When access_token is expired, we make a call to token endpoint with refresh_token flow as explained in the above topic.

But if refresh_token is also expired, we will not get the access_token.

We can determine that the refresh token is expired when we get the below error message in refresh-token call.

{
    "error":"invalid_grant",
    "error_description":"authentication failure"
}

Once we get this, we'll have to start the process of retrieving authorization code again and make a call to token endpoint with the token as mentioned in Part 1 of this blog.

By this way, In PKCE, we can generate access_token to authenticate an user by just using code verifier and code challenge which is randomly generated, which makes the authentication process smooth and secure as we are not storing any secrets in the browser.

Reference:

0
Subscribe to my newsletter

Read articles from Shreyas sk directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Shreyas sk
Shreyas sk