Paul Hadfield
Paul Hadfield

Reputation: 6136

Getting a Refresh Token from NextAuth Okta Provider

We have an existing application use NextAuth to communicate with Okta. This is all working fine, we've just added an API which needs the Okta Access Token, we've used the JWT callback in NextAuth to grab the access token from the account object, and add that to the NextAuth session.

This is all working fine, we can grab the access token from the session and pass that to the API and is all good, the API validates the access token and all is fine. But, we sometimes come back to the session and the call to the API fails because it can not validate the access token as it has expired.

I'm looking at the NextAuth documentation for rotating a refresh token and I see that Okta has an end-point for getting a new refresh token. That all seems to make sense, the problem is, looking at what the JWT callback in NextAuth returns, I don't get a refresh token, these are the props returned by the Okta provider. We get an access token, and an ID token (they do contain different values) but we don't get a refresh token returned.

I see reference to offline_access scope with regards to refresh tokens, do we need to set this in our call for the Okta provider? If so, how? Otherwise, has anyone managed to use the Okta Provider in NextAuth and fixed the expired access token issue?

{
  token: {
    name: ...,
    email: ...,
    picture: .,
    sub: ...
  },
  user: {
    id: ..,
    name: ...,
    email: ...,
    image: undefined
  },
  account: {
    provider: 'okta',
    type: 'oauth',
    providerAccountId: ...,
    token_type: 'Bearer',
    expires_at: ...,
    access_token: ...,
    scope: ...,
    id_token: ...
  },
  profile: {
    sub: ...,
    name: ...,
    email: ...,
    ver: ...,
    iss: ...,
    aud: ...,
    iat: ...,
    exp: ...,
    jti: ...,
    amr: [ ... ],
    idp: ...,
    preferred_username: ...,
    auth_time: ...,
    at_hash: ...
  },
  isNewUser: undefined
}

Upvotes: 5

Views: 1933

Answers (2)

tdesmond
tdesmond

Reputation: 61

First off, thanks! I got this working with the snippet provided by Appu Rajendran but I wanted to add the logic that I also had to write into the jwt callback to refresh the access token once I had the refresh token.

async jwt({ token, account }: any) {
if (account) {
    token.accessToken = account.access_token;
    token.idToken = account.id_token;
    token.oktaId = account.providerAccountId;
    token.refreshToken = account.refresh_token;
}

var tokenParsed = JSON.parse(Buffer.from(token.idToken.split('.')[1], 'base64').toString());
const dateNowInSeconds = new Date().getTime() / 1000
const tokenIsNotExpired = dateNowInSeconds < tokenParsed.exp;
if (tokenIsNotExpired) {
    return token;
} else {
    const url = `${process.env.OKTA_DOMAIN}/v1/token`;
    const body = `grant_type=refresh_token&client_id=${process.env.OKTA_CLIENTID}&client_secret=${process.env.OKTA_CLIENTSECRET}&refresh_token=${token.refreshToken}`;
    const headers = {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Accept': 'application/json'
    };
    const response = await fetch(url, { method: 'POST', body, headers });
    const data = await response.json();
    if (data.error) {
        throw new Error("Unable to refresh token");
    }
    token.accessToken = data.access_token;
    token.idToken = data.id_token;
    token.oktaId = data.providerAccountId;
    token.refreshToken = data.refresh_token;

    return token;
}

},

I did a full writeup explaining the steps here: https://thetombomb.com/posts/next-okta-refresh-token. The article also includes more information on the scope property.

Upvotes: 1

Appu Rajendran
Appu Rajendran

Reputation: 330

You may add the scope like below ‘

OktaProvider({
  clientId: process.env.OKTA_CLIENT_ID,
  clientSecret: process.env.OKTA_CLIENT_SECRET,
  issuer: process.env.OKTA_ISSUER,
  idToken: true,
  exp: true,
  authorization: { 
    params: { 
      scope: "openid offline_access" 
    } 
  }
})

Upvotes: 6

Related Questions