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 - Silent SSO could not be completed, when token expired #2934

Closed MarekLani closed 3 years ago

MarekLani commented 3 years ago

Library

Framework

Description

I am developing react app and authenticating user against AAD B2C using react msal library. I was able to make the authentication working and I am able to acquire token silently, however I am facing issue when original token expires:

BrowserAuthError: silent_sso_error: Silent SSO could not be completed - insufficient information was provided. Please provide either a loginHint or sid.

I assume this has something to do with token not being refreshed. Is there anything I should do explicitly, so that token gets refreshed?

Error Message

BrowserAuthError: silent_sso_error: Silent SSO could not be completed - insufficient information was provided. Please provide either a loginHint or sid.

MSAL Configuration

const tenant = "mytenant.onmicrosoft.com";

const signInPolicy = "B2C_1A_signup_signin";
const applicationID = "<appid>";
const reactRedirectUri = "http://localhost:1234/home";
const tenantSubdomain = tenant.split(".")[0];
const instance = `https://${tenantSubdomain}.b2clogin.com/`;
const signInAuthority = `${instance}${tenant}/${signInPolicy}`;// Msal Configurations
const authorityDomain = "mytenant.b2clogin.com"
// Config object to be passed to Msal on creation
export const msalConfig = {
  auth: {
      clientId: applicationID,
      authority: signInAuthority,
      knownAuthorities: [
        authorityDomain
      ]
  }
};

export const loginRequest: RedirectRequest = {
  scopes: ["https://mytenant.onmicrosoft.com/api/read"]
};

Reproduction steps

Login using sample code Wait for 24 hours for token to expire Issue appears

    React.useEffect(() => {
        if (account && inProgress === "none")
            instance.acquireTokenSilent({
                ...loginRequest,
                account: account
            }).then((response) => {
                ...
            })

    }, [account])

Expected behavior

AcquireTokenSilently would acquire the token

Identity Provider

Browsers/Environment

Regression

Security

Source

tnorling commented 3 years ago

@MarekLani You have 3 options in this scenario:

  1. Configure your tenant to return the emails claim on idTokens. Read more here
  2. Explicitly include loginHint with the user's email on the request object passed to acquireTokenSilent
  3. Call an interactive method such as acquireTokenPopup or acquireTokenRedirect when acquireTokenSilent fails. (We recommend you do this anyway, in addition to one of the first 2)
MarekLani commented 3 years ago

@tnorling thank you for response, however I am not able to set loginHint on acquireTokenSilent, it seems not to be valid parameter (I am using typescript)

tnorling commented 3 years ago

@MarekLani My mistake, you're right loginHint is not part of the acquireTokenSilent request object. You should use the account object and ensure the username field is populated. You can do this either by enabling the emails claim on your B2C tenant and MSAL will handle this automatically, or you can manually update the username property with the user's login before passing it to acquireTokenSilent

MarekLani commented 3 years ago

Thank you @tnorling, I was trying to enable emails claim in my custom policy, but I probably made mistake somewhere, can't find documentation on doing it, but will try to manually set the username using email from idTokenClaims. Will report back

code-by-gijs commented 3 years ago

The accesstoken will always expire after 24 hours, even if you refresh your tokens. There is no rolling window. This is how the flow works within a SPA. As @tnorling explained, you should trigger an interactive flow when acquireTokenSilent fails.

offtopic: I really hope this will be changed within b2c... It's really hard to create a user friendly app when your token expires every 24 hours.

MarekLani commented 3 years ago

@code-by-gijs thanks for additional context, would this be helpful in this case: https://docs.microsoft.com/en-us/azure/active-directory-b2c/session-behavior?pivots=b2c-custom-policy

tnorling commented 3 years ago

The accesstoken will always expire after 24 hours, even if you refresh your tokens. There is no rolling window. This is how the flow works within a SPA. As @tnorling explained, you should trigger an interactive flow when acquireTokenSilent fails.

The refresh token expires after 24 hours. Access token lifetimes are 1 hour.

offtopic: I really hope this will be changed within b2c... It's really hard to create a user friendly app when your token expires every 24 hours.

When the refresh token expires after 24 hours msal.js will attempt to silently acquire a new set of tokens (including refresh token) using a hidden iframe, this is what @MarekLani was asking about. This will not work if you don't have a login_hint or if your browser blocks 3rd party cookies in which case you will need to invoke an interactive flow.

@MarekLani If you enable the emails claim on your id tokens this should just work, assuming 3rd party cookies are not blocked. There's some short instructions here, however, if you need more help getting this setup you can contact B2C Support as they are more knowledgeable than me about B2C policy setup.

code-by-gijs commented 3 years ago

Just to add on to what @tnorling has said, the auth server will send a new refresh token when you use one to renew an access token. So if you call acquireTokenSilent at least once every 24 hours, your application will not perform an interactive flow. The only time you will need to handle the acquireTokenSilent failure with an interactive call is if it has not been called within 24 hours of a token retrieval or renewal.

Nope, you can refresh a token for 24 hours, after those 24 hours, you cannot refresh your token anymore. It is not a rolling window for a SPA. So every 24 hours you will have to perform an interactive flow.

pkanher617 commented 3 years ago

My mistake! I was under the impression the new refresh token had a new expiration. please disregard my comment.

tnorling commented 3 years ago

So every 24 hours you will have to perform an interactive flow.

@code-by-gijs This is not accurate. See my comment above regarding hidden iframes.

code-by-gijs commented 3 years ago

So every 24 hours you will have to perform an interactive flow.

@code-by-gijs This is not accurate. See my comment above regarding hidden iframes.

Hidden iframes are not the way to go. They require third party cookies. More and more browsers are dropping support for third party cookies by default.

pkanher617 commented 3 years ago

Unfortunately there isn't another workaround at the moment. The limitation of 3p cookies being blocked is going to be more prevalent, as you called out. We are working on solutions that may leverage Storage Access API, but these are still in design and will need time from the server team and the MSAL team to implement correctly. Until then the only way to handle this is to make a request using the redirect or popup APIs every 24 hours.

RonniePitts commented 3 years ago

The expired acquireTokenSilent call does return the error code from the 400 response however this information isnt available in the catch instead it returns "Silent SSO could not be completed - insufficient information was provided. Please provide either a loginHint or sid."

The actual response from the failed request :

error: "invalid_grant",…} error: "invalid_grant" error_description: "AADB2C90080: The provided grant has expired. Please re-authenticate and try again. Current time: 1614680980, Grant issued time: 1614431911, Grant expiration time: 1614514326 ↵Correlation ID: xxxxxx ↵Timestamp: 2021-03-02 10:29:40Z ↵"

It would be nice to capture these details as the current errormessage is misleading.

@MarekLani, how do you send across the email address from social accounts, for example google?

tnorling commented 3 years ago

@RonniePitts What you're experiencing is by design and the error that you're receiving is telling you what's not working. This happens because acquireTokenSilent internally catches the invalid_grant error and attempts to silently resolve it by using a hidden iframe to acquire new access and refresh tokens. It's this part that is now failing with the insufficient information error because the account object you passed to acquireTokenSilent does not have a username field (which is used as the loginHint metioned in the error). Since you're using B2C you will need to enable the emails claim on your idTokens to ensure your account contains the necessary fields. More information on that can be found here

RonniePitts commented 3 years ago

@tnorling, doesnt passing the email address become an issue when you may have mutiple social accounts with the same email address? Is it possible to pass across the localAccountId instead which is available from the prior cache object?

tnorling commented 3 years ago

@RonniePitts Email is currently the only session identifier the service accepts. With that said, the B2C service doesn't do anything with the loginHint MSAL sends them in most scenarios, they rely on the cookies set during login. The error thrown in the library is to make sure that developers are passing some session identifier as it is used, and required, in other non-B2C scenarios. The multiple account scenario you call out is definitely a valid concern and we're working with the B2C service team to improve this experience. If you would like to give them your feedback directly and/or get some support you can open a ticket with the service team by following these instructions

vinizinmoraes-concrete commented 3 years ago

@RonniePitts What you're experiencing is by design and the error that you're receiving is telling you what's not working. This happens because acquireTokenSilent internally catches the invalid_grant error and attempts to silently resolve it by using a hidden iframe to acquire new access and refresh tokens. It's this part that is now failing with the insufficient information error because the account object you passed to acquireTokenSilent does not have a username field (which is used as the loginHint metioned in the error). Since you're using B2C you will need to enable the emails claim on your idTokens to ensure your account contains the necessary fields. More information on that can be found here

There is any tutorial to show how to accomplish it using a custom user flow ? My user flow need to be changed in a xml way, but I don`t know how to do it.

tnorling commented 3 years ago

@vinizinmoraes-concrete You should open a ticket with the B2C service and they should be able to help you configure your user flow.

As the original question has been answered I'm going to go ahead and close this issue. Please open a new issue if you have additional non-service related questions, thanks!