AzureAD / microsoft-authentication-library-for-js

Microsoft Authentication Library (MSAL) for JS
http://aka.ms/aadv2
MIT License
3.64k stars 2.65k forks source link

React MSAL - Clarifications needed on working of acquireTokenSilent #2979

Closed shyam2794 closed 3 years ago

shyam2794 commented 3 years ago

Library

Description

Need clarification in understanding the acquireTokenSilent process . And the recommended approach to use renew the access token.

This is my auth Config

export const msalConfig = {
  auth: {
    clientId: '**************************',
    authority:
      'https://login.microsoftonline.com/**********************',
    redirectUri: `${window.location.origin}/auth/return`,
  },
  cache: {
    cacheLocation: 'localStorage', // This configures where your cache will be stored
    storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
  },
  system: {
    loggerOptions: {
      loggerCallback: (level, message, containsPii) => {
        if (containsPii) {
          return;
        }
        switch (level) {
          case LogLevel.Error:
            console.error(message);
            return;
          case LogLevel.Info:
            console.info(message);
            return;
          case LogLevel.Verbose:
            console.debug(message);
            return;
          case LogLevel.Warning:
            console.warn(message);
            return;
        }
      },
      logLevel: LogLevel.Verbose,
    },
  },
};

// Please note i am using the same set of scope for the initial login as well as for the acquireTokenSilent . 
export const loginRequest = {
  scopes: ['https://*********************/.default'],
};

we are using axios for firing API's , have added the token refresh logic before making the API call's ( gone through the docs and this was the recommended approach ) . Below is my axios interceptor code

axios.interceptors.request.use(
  async (config) => {
    config.headers['x-api-challenge'] =
      localStorage.getItem('challenge') || 'no-key-found';
    const token = await refreshAccessToken();
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

Below is the util we have for the refreshAccessToken

 export const refreshAccessToken = async () => {
  const account = getAccountInfo();
  try {
    const token = await msalInstance.acquireTokenSilent({
      account,
      scopes: loginRequest.scopes,
    });
    return token.accessToken;
  } catch (error) {
    if (error instanceof InteractionRequiredAuthError) {
      return msalInstance
        .acquireTokenPopup(loginRequest)
        .then((resp) => {
          return resp.accessToken;
        })
        .catch((err) => {
          routeToHome();
          console.log(err);
        });
    } else {
      routeToHome();
      console.log(error);
    }
  }
};

Question

  1. The acquireTokenSilent seems to work fine when we are disabling the 3rd party cookies in the browser. But i see some errors in the console . Why we see these errors ? image

  2. If we disable the 3rd party cookies option in browser acquireTokenSilent fails and the user gets re-directed to the login page for every 1 hr. Is there an alternate approach to do silent token refresh without wanting the user to enable the cookies ?

  3. What is the expiration time for the refresh token and access token ? . what will happen if the refresh token fails in the middle of a session and if the user is trying to make a API Call ?

Source

hectormmg commented 3 years ago

Hi @shyam2794 .

The acquireTokenSilent API does the following:

  1. It checks the cache to see if there's a valid, unexpired access token that matches the request passed in. If there is, it returns said access token.
  2. In case the access token is expired, it uses the cached refresh token to request a new Access Token from the network and returns the new Access Token.
  3. If the refresh token is not valid, acquireTokenSilent attempts to request a new Access Token and Refresh Token pair by opening a hidden iframe. This is the case in which 3rd-party cookies being disabled would cause an error.

A small clarification: Your method is called "refreshAccessToken", however, it would be more appropriate to call it "getToken". The acquireTokenSilent API is designed to retrieve tokens when they are cached and refresh them through a network call when they expire, but the token refreshing is abstracted away so you don't have to worry about it.

The expiration time for an Access Token is 1hr and the refresh token has a 24 hour, non-sliding expiration time. That means that even if you renew a refresh token before it expires, the new refresh token will have the same expiration time as the previous one.

In order to be able to investigate your specific issue (the error shown on the token request), we'd need a network trace so we can see what went wrong. You can e-mail me a Fiddler trace (e-mail is on my profile) and I'll take a look!

shyam2794 commented 3 years ago

@hectormmg Currently we are seeing some issues were the users are getting kicked out to the login page in a random way . I am pretty confident that its not because of the access token issue coz if that's the case then the user will be kicked out for every 1 hour ( access token expires for every 1 hour ) . So need to check whether this has to do with getting the account .

 const getAccountInfo = () => {
  const accounts = msalInstance.getAllAccounts();
  return accounts[0]; // Is this the correct way ? . 
};

const refreshAccessToken = async () => {
  const account = getAccountInfo();
  try {
    const token = await msalInstance.acquireTokenSilent({
      account,
      scopes: loginRequest.scopes,
    });
    return token.accessToken;
  } catch (error) {
    if (error instanceof InteractionRequiredAuthError) {
      return msalInstance
        .acquireTokenPopup(loginRequest)
        .then((resp) => {
          return resp.accessToken;
        })
        .catch((err) => {
          routeToHome();
          console.log(err);
        });
    } else {
      routeToHome();
      console.log(error);
    }
  }
};
  1. If 3 users login to the app what does msalInstance.getAllAccounts() give ? .
shyam2794 commented 3 years ago

That means that even if you renew a refresh token before it expires, the new refresh token will have the same expiration time as the previous one.

So if i clear the cache in the browser after 22hours and hit the app again will the new refresh token will have only 2 hours left to expire ?

pkanher617 commented 3 years ago

@shyam2794 No, it will only do this if you attempt to renew the token with a refresh token. If you make an authorize call with either login/acquireTokenRedirect, login/acquireTokenPopup or ssoSilent, you will receive a refresh token with a new full 24hr lifetime. If you only attempt to renew the token with the refresh token (calling acquireTokenSilent) you will not receive a refresh token for a full 24 hour lifetime, only the duration left on the original token call. Hope this makes sense.

github-actions[bot] commented 3 years ago

This issue has not seen activity in 14 days. If your issue has not been resolved please leave a comment to keep this open. It will be closed in 7 days if it remains stale.

github-actions[bot] commented 3 years ago

This issue has been closed due to inactivity. If this has not been resolved please open a new issue. Thanks!