Adaptive Token Refresh

Introduction

The OAuth specification describes a Refresh Token flow which allows an application to use a long-lived Refresh Token to obtain a new set of tokens when the short-lived Access Token has expired.

This flow might be used within an active application session if the associated Access Token expires.
This flow might also be used to create a new application session from a stored Refresh Token.

When running the Refresh Token flow for an application configured for policy-based authentication, the policy may deny access or require additional authentication.

This guide shows how to handle this flow for a NodeJS application using the Adaptive Proxy SDK.

Prerequisites

Running the flow in this guide requires that you have an application set up for policy-based authentication and on-boarded for Adaptive Access. This is covered in On-boarding a native application.

The code shown in this guide is an addition to the NodeJS application described in Set up a sample application.

If you want to show the explore the full refresh (where a new session is created), you must have completed the Performing Recollection guide to set up the collection logic in the sample application.

Enable application for adaptive refresh flow

In this section you will turn on Refresh Tokens for the sample application and apply access policies for the Refresh Token flow.

Navigate to the application sign-on settings

In the Admin UI of your IBM Security Verify tenant, navigate to the Applications page.
Locate the definition for the sample application and select the Settings icon.
In the application definition, select the Sign-on tab.

1125

Navigate to sign-on settings

Enable Refresh Tokens in sign-on settings

Enable the checkbox for Generate refresh token to enable the generation of refresh tokens and to enable the refresh token flow.

Apply access policies to refresh token flow

By default, the policy-based authentication rules are not applied to refresh token flows. This means that Adaptive Access is not invoked for this flow.

Locate the Apply access policy to API grant types setting and enable the checkbox for Refresh token.

Save the updated application definition.

1008

Enable adaptive refresh token flow

Add refresh code to sample application

In the sample application, add the following code to the end of the index.js file. This code initates a refresh token flow using the Adaptive Proxy SDK.

📘

Notice the code reuse

The handleEvaluateResult function that already exists to handle the response from the evaluatePassword function is reused here to handle the response from the adaptive refresh function. It will deny access, allow access, or invoke 2FA as required.

// Function that performs Refresh Flow
async function doRefresh(req, res) {

  if (!req.session.sessionId || !req.session.refresh_token) {
    res.status(400).send("<html>Cannot Refresh</html>");
    return
  }

  // Set up context required for call to Adaptive SDK
  var context = {
    sessionId: req.session.sessionId,
    userAgent: req.headers['user-agent'],
    ipAddress: req.ip
  };

  try {

    // Call refresh function
    var result = await adaptive.refresh(context, req.session.refresh_token);

    handleEvaluateResult(req, res, context, result);

  } catch (error) {
    console.log(error);
    if (error.response.data.messageDescription) {
      res.status(403).send({error: error.response.data.messageDescription});
    } else {
      res.status(403).send({error: error.message});
    }
  }
  delete req.session.refresh_token
}

In-session refresh

Usually an in-session refresh would be invoked by application logic when the Access Token in the session has expired. To allow you to see the refresh in action, you will add a /refresh endpoint to allow a manual trigger of the refresh function.

Add the following code to the end of the index.js file:

// GET /refresh performs a refresh operation using the refresh token
// that was returned during login and the existing Adaptive SessionId
app.get("/refresh", (req, res) => {

  // If no refresh token, redirect for full login
  if (!(req.session.token && req.session.token.refresh_token)) {
    res.redirect('/login');
    return
  }
  req.session.refresh_token = req.session.token.refresh_token;
  doRefresh(req, res);
});

New-session refresh

In order to run a refresh token flow in a new session, the Browser SDK must be re-run in order to re-collect browser data. This is done using the re-collection logic described in Performing Recollection.

Usually a new session refresh would be run using an encrypted refresh token pulled from secure storage. In this sample application the refresh token must be provided as a query-string parameter:

/fullrefresh?token=<refresh_token>

❗️

This is not recommended practice for a real application

Presenting the refresh_token in a query string in this way is not secure and is not recommended for any real application. The sample application is set up this way to allow a simple way to initiate the full refresh flow.

Add the following code to the end of the index.js file. This will initiate a collection flow but will set a session variable so that a refresh is triggered when collection is complete.

// GET /fullrefresh performs a refresh operation using provided refresh token.
// Browser collection is performed to get fresh adaptive sessionId
app.get("/fullrefresh", async (req, res) => {

  if (!(req.query.token)) {
    res.status(400).send("<html>Expected ?token=[refresh_token]</html>");
    return
  }
  // Save refresh token to session
  req.session.refresh_token = req.query.token;
  // Save indicator for post-collection operation
  req.session.postcollect = "refresh";
  res.redirect('/collect');
});

Test the refresh flows

Start the application and perform initial login

Start the updated sample application:

node index.js

In a browser, connect to the application and log in. When login is complete, the session token object will be shown in the browser:

{
    "access_token": "gJduCYHPK0jG7RBiMSiQ3WtGQvxUzpp4GtqTZi8s",
    "refresh_token": "gj4490jHIOEIH93jgj239nnv8u48nv4892g8t9hnv29",
    "scope": "openid",
    "grant_id": "24c6fa4e-dca5-4876-bd24-6b897920d74b",
    "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNlcnZlciJ9.eyJhY3IiOi...HAiOiAxNTk5MTAxMzY3fQ.HZ9LE3Hb3GkXD..7sQJFlXoXGP3w",
    "token_type": "Bearer",
    "expires_in": 7199
  }

Perform in-session refresh

Note the access_token and refresh_token values (so that you will know that they have changed after the refresh).

Naviate to /refresh. This will initiate the in-session token refresh. After the refresh you are returned to the home page and the updated tokens are shown.

Perform new-session refresh

Copy the value of the refresh_token shown on the home page to your clipboard.

Stop and restart the sample application to clear all session data.

In the browser, initiate the new-session refresh by navigating to the following URL (replacing refresh_token for the refresh token you copied above.

/fullrefresh?token=refresh_token

The application will redirect you to the re-collection page to collect updated browser data. The session Id is captured from the POST response and added to the session. The refresh logic is invoked.

It is likely that in this same browser session the Adaptive risk will be low and your session will be established without requiring any additional authentication.

Copy the new refresh_token to your clipboard.

Open a new browser (ideally a different browser that you haven't used with this application before) and navigate to the following URL (once again replacing refresh_token for the refresh token you copied above.

/fullrefresh?token=refresh_token

It is likely that in this new browser session the Adaptive risk will be high and you will be required to complete an e-mail OTP challenge before your session is established.