简体   繁体   中英

NextAuth refresh token with Azure AD

Since the default valid time for an access token is 1 hour, I am trying to get refresh tokens to work in my application. I have been stuck with this problem for a couple of weeks now, and cannot seem to fix it. I have verified my refreshAccessToken(accessToken) function to work (where accessToken is an object with the expired token, the refresh token and some other stuff).

I pinpointed the issue to the async session() function. While in the async jwt() function, refreshAccessToken gets called, the async session() function still results in an error, because the parameter token is undefined . This results in the error cannot read property accessToken from undefined (since token.accessToken is on the first line). How could I solve this, and somehow make the function async session() wait for the access token to refresh, before sending it to the client, together with all other information (groups, username etc.)?

/**
 * All requests to /api/auth/* (signIn, callback, signOut, etc.) will automatically be handled by NextAuth.js.
 */

import NextAuth from "next-auth"
import AzureAD from "next-auth/providers/azure-ad";

async function refreshAccessToken(accessToken) {
  try {
    const url = "https://login.microsoftonline.com/02cd5db4-6c31-4cb1-881d-2c79631437e8/oauth2/v2.0/token"
    await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
      body: `grant_type=refresh_token`
      + `&client_secret=${process.env.AZURE_AD_CLIENT_SECRET}`
      + `&refresh_token=${accessToken.refreshToken}`
      + `&client_id=${process.env.AZURE_AD_CLIENT_ID}`
    }).then(res => res.json())
      .then(res => {
        return {
          ...accessToken,
          accessToken: res.access_token,
          accessTokenExpires: Date.now() + res.expires_in * 1000,
          refreshToken: res.refresh_token ?? accessToken.refreshToken, // Fall backto old refresh token
        }
      })
  } catch (error) {
    console.log(error)

    return {
      ...accessToken,
      error: "RefreshAccessTokenError",
    }
  }
}

export const authOptions = {
  providers: [
    AzureAD({
      clientId: process.env.AZURE_AD_CLIENT_ID,
      clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
      tenantId: process.env.AZURE_AD_TENANT_ID,
      authorization: {
        params: {
          scope: "offline_access openid profile email Application.ReadWrite.All Directory.ReadWrite.All " +
            "Group.ReadWrite.All GroupMember.ReadWrite.All User.Read User.ReadWrite.All"
        }
      }
    }),
  ],
  callbacks: {
    async jwt({token, account, profile}) {
      // Persist the OAuth access_token and or the user id to the token right after signin
      if (account && profile) {
        token.accessToken = account.access_token;
        token.accessTokenExpires = account.expires_at * 1000;
        token.refreshToken = account.refresh_token;

        token.id = profile.oid; // For convenience, the user's OID is called ID.
        token.groups = profile.groups;
        token.username = profile.preferred_username;
      }

      if (Date.now() < token.accessTokenExpires) {
        return token;
      }

      return refreshAccessToken(token);
    },
    async session({session, token}) {
      // Send properties to the client, like an access_token and user id from a provider.
      session.accessToken = token.accessToken;
      session.user.id = token.id;
      session.user.groups = token.groups;
      session.user.username = token.username;

      const splittedName = session.user.name.split(" ");
      session.user.firstName = splittedName.length > 0 ? splittedName[0] : null;
      session.user.lastName = splittedName.length > 1 ? splittedName[1] : null;

      return session;
    },
  },
  pages: {
    signIn: '/login',
  }
}

export default NextAuth(authOptions)

I tried searching everywhere online, but it is hard to even find people using Azure AD with NextAuth at all (NOT the B2C version, but the B2B/organisations version).

TLDR: Using the refreshToken to get a new accessToken works, but NextAuth does not pass this token to the frontend, and instead throws an error, since in async session() , token is undefined.

In the provided code, I made the mistake of mixing async with promises (credits to @balazsorban44 on GitHub). This means that the return statement in the .then returns the value to where the fetch request was initiated, instead of it returning a value from the function refreshAccessToken() as a whole. I moved away from using promises and only used an async function:

    const req = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
      body: `grant_type=refresh_token`
      + `&client_secret=${process.env.AZURE_AD_CLIENT_SECRET}`
      + `&refresh_token=${accessToken.refreshToken}`
      + `&client_id=${process.env.AZURE_AD_CLIENT_ID}`
    })

    const res = await req.json();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM