AADB2C90080: The provided grant has expired. Please re-authenticate and try again - azure-ad-b2c

We are using azure/msal-angular v2.3 with ADB2C Custom policies having MFA enabled with either Phone or Email method.
Many LIVE users are reporting issues while logging in.
At times they need to do MFA twice (at times thrice) to get into the application.
After digging into the audit logs from b2c and HAR file from the customer we observed below error is being raised in User Journey.
{
"error": "invalid_grant",
"error_description": "AADB2C90080: The provided grant has expired. Please re-authenticate and try again. Current time: 1676016884, Grant issued time: 1675941825, Grant expiration time: 1675951412\r\nCorrelation ID: 6052f247\r\nTimestamp: 2023-02-10 08:14:44Z\r\n"
}
As per my understanding msal 2.x automatically handles the token refresh and we don’t need to implement any code for acquiring tokens silently. Don't know why it is expring.
Is it affecting if user keeps the screen idle for long?
Any help to resolve this is appreciated , Thanks in advance.

You are correct that msal 2.x automatically takes care to refresh token. But in few cases when user is inactive for long time or when access token expiry is less than the refresh token default time set.
In that case, acquireTokenSilent() method can be used to obtain a new token.To obtain a new access token silently, call the acquireTokenSilent() method of the MsalService with the desired scopes.
Code:
getToken() {
this.authService.acquireTokenSilent({
scopes: ['<your-scope>'],
}).then((accessToken: string) => {
// Use the access token // console.log('New token:', token);
}).catch((error: any) => {
console.log(error);
});
Check expiration times of the access and refresh tokens, and refresh interval set. Adjust these settings using accessTokenExpirationOffsetInSeconds property to tell number of seconds/minutes before which refresh token has to be set.
this.authService.init({
auth: {
clientId: '<your-client-id>',
authority: '<your-b2c-tenant>.b2clogin.com/<b2c-tenant>.onmicrosoft.com/B2C_1_<policy-name>',
redirectUri: '<your-redirect-uri>',
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: true,
accessTokenExpirationOffsetInSeconds: 300,
},
});
}
In this MSAL will try to refresh the access token within 5 minutes ~ 300 seconds before it expires.
This error or failure can happen if the user takes too long to complete the MFA flow.
For that ,In custom policy set the timeout for mutifactor authentication to 5 minutes to give minimum time for user to authenticate.
<OrchestrationStep Order="2" Type="MultiFactorAuthentication">
<MultiFactorAuthentication>
<AuthenticationMethods>
<AuthenticationMethod ReferenceId="phoneFactor" />
<AuthenticationMethod ReferenceId="emailFactor" />
</AuthenticationMethods>
<FailureModes>Deny</FailureModes>
<Default>false</Default>
<SendErrorCodesToResponse>false</SendErrorCodesToResponse>
<Enrollment>Conditional</Enrollment>
<phoneFactor-Timeout>300</phoneFactor-Timeout>
<emailFactor-Timeout>300</emailFactor-Timeout>
</MultiFactorAuthentication>
By theses token refresh behavior can be contolled.
Reference :
Configure authentication in a sample single-page application by using Azure Active Directory B2C | Microsoft Learn
Configure session behavior - Azure Active Directory B2C | Microsoft Learn

Related

Azure B2C: KMSI feature does not work with custom policy

Our SPA uses Azure B2C and MSAL (React) for user authentication. There are other requirements so we use custom policies instead of predefined user flows. But I struggle to implement Keep Me Signed In (KMSI) feature following these instructions.
I used custom policies from the starter pack: Phone_Email_Base.xml and SignUpOrSignInWithPhoneOrEmail.xml
Added <Item Key="setting.enableRememberMe">True</Item> entry to <TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Phone-Email">
Updated relying party policy file with this:
<UserJourneyBehaviors>
<SingleSignOn Scope="Tenant" KeepAliveInDays="30" />
<SessionExpiryType>Absolute</SessionExpiryType>
<SessionExpiryInSeconds>1200</SessionExpiryInSeconds>
<ScriptExecution>Allow</ScriptExecution>
</UserJourneyBehaviors>
Set up MSAL instance in my index.tsx following this. Lib versions: "#azure/msal-browser": "^2.14.2", "#azure/msal-react": "^1.0.0"
Tried to obtain access token:
msalInstance
.acquireTokenSilent(accessTokenRequest)
.then((response) => {
// use response.accessToken here
...
})
.catch((e) => {
console.error(e);
if (e instanceof InteractionRequiredAuthError) {
instance.acquireTokenRedirect(accessTokenRequest);
}
});
The problem is MSAL cannot retrieve access token silently after 24 hours from user logged in (i.e. once refresh token is expired) and requires user to re-login.
To make sure that my application code is Ok, I tried to use predefined user flow (combined B2C_1_SignUpSignIn) with KMSI feature enabled. And in this case, my application is able obtain access token silently after 24 hours. So KMSI works perfectly with user flow, but doesn't with custom policy.
Crawled through docs and examples for days, but still can't find any clues what else needs to be done here. Any help would be appreciated.
When acquireTokenSilent() fails, MSAL will call ssoSilent(). This will launch a hidden iframe to try to get a token using cookie based SSO.
When this fails, a final error will come back. You must catch this error and call acquireTokenRedirect(). Now if your session setup for your technical profiles is setup properly, and a valid session cookie exists, you’ll get SSO.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/custom-policy-reference-sso
If you are actually seeing a prompt for user input, your session setup is not correct for that particular techical profile. This is the real reason why ssoSilent() failed.
Your problem is not KMSI. To prove it, remove KMSI config, sign in to your app, remove the MSAL objects from the LocalStorage, force a token renewal. You’ll reproduce the issue you described, even without KMSI, and just after a few minutes of logging in.
Well, eventually it turned out that this solution actually works. Still not sure why it failed after the first 24 hours after the custom policy was applied. So I was forced to re-login after the first 24 hours but when the other 24 hours passed, my application was able to get a new access token without providing credentials by the user.

invalid_grant error trying to refresh access token for googleapis

We use the googleapis for our web app and store and manage the access token and refresh token to our database.
When we refresh the access token of users using refresh token, rarely we had the GaxiosError: invalid_grant.
Now, we use the google api nodejs client[ https://github.com/googleapis/google-api-nodejs-client ].
We store the access token and the refresh token to our database for each user and they are updated by bellow logic per 6 hours.
import { google } from 'googleapis';
// create oauth2client for google apis
const oAuth2Client = new google.auth.OAuth2(client_secret, client_id, redirect_uri);
// set current access token and refresh token (==current_tokens)
oAuth2Client.setCredentials(current_tokens);
// refresh access token and refresh token
// new_token contains access_token and refresh_token
const new_token = await oAuth2Client.refreshAccessToken();
// store the new access token and new refresh token to database
...
Does anyone know what may be causing GaxiosError: invalid_grant?
I'm getting the feeling that it may be due to the refresh token being updated every 6 hours.
Additional Info
the setting of generating auth url
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: GOOGLE_APIS_SCOPE, // GOOGLE_APIS_SCOPE contains scopes
prompt: 'consent',
});
There are a number of reasons why a refresh token will expire If we check the documentation for Oauth2 You will find a list of them here.
The user has revoked your app's access.
The refresh token has not been used for six months.
The user changed passwords and the refresh token contains Gmail scopes.
The user account has exceeded a maximum number of granted (live) refresh tokens.
The user belongs to a Google Cloud Platform organization that has session control - policies in effect.
A Google Cloud Platform project with an OAuth consent screen configured for an external user type and a publishing status of "Testing" is issued a refresh token expiring in 7 days.
Currently the most common reason would be that your application is not set to production if its still in testing then your refresh token will expire in a week.
You mention that its stored in the database every six hours. I would double check if you are refreshing the access token every six hours and that it does return a new refresh token each time that you are in fact updating the database with the most current refresh token otherwise you may be reaching the "maximum number of granted (live) refresh tokens"

AzureAD MaxAgeSessionMultiFactor Not Working

We are using Configurable token lifetimes in Azure Active Directory to change the max session age for multi-factor scenarios. While I've updated the policy with the command below, and confirmed that the access token lifetime changed from the default 60 minutes to 30 minutes, it did not change to 10 minutes as the policy indicates it should below. I've also confirmed it is the default org policy.
Set-AzureADPolicy -Id <OBJECT ID> -DisplayName "OrganizationDefaultPolicyUpdatedScenario" -Definition #('{"TokenLifetimePolicy":{"Version":1,"AccessTokenLifetime":"00:30:00","MaxAgeMultiFactor":"00:11:00","MaxAgeSessionMultiFactor":"00:10:00"}}')
I've checked the JWT token and confirmed exp is 30 minutes, so AccessTokenLifetime is implemented, but I'm using Outlook to test and confirmed MFA. The amr claim is "pwd,mfa".
Indirectly I think the MaxAgeMultiFactor timeout is working, but the session timeout is longer so it is hard to tell.
Why isn't MaxAgeSessionMultiFactor at 10 minutes (the minimum) working?
What am I doing wrong?
Complete policy:
class Policy
{
Id = 2a094bfe-d74e-4d55-906f-7cef8e54746b
OdataType =
AlternativeIdentifier =
Definition =
[
{
"TokenLifetimePolicy":
{
"Version":1,
"AccessTokenLifetime":"00:30:00",
"MaxAgeMultiFactor":"00:11:00",
"MaxAgeSessionMultiFactor":"00:10:00"
}
}
]
DisplayName = OrganizationDefaultPolicyUpdatedScenario
IsOrganizationDefault = True
KeyCredentials =
[
]
Type = TokenLifetimePolicy
}
Generally once access token is obtained, Azure AD will only check for the refresh token at the time of renewal. If the refresh token is also expired, Azure AD will then force the user for a fresh auth and check if MFA is required. If MFA is required, Azure AD will look to see if MFA cookie exists, MFA cookie is valid or not etc.
I see that you have only modified the access token lifetime and MFA lifetimes. So unless your MaxAgeSessionSingleFactor lifetime (Refresh Token) is less than MaxAgeSessionMultiFactor user will not be affected or prompted for MFA. Also single-factor authentication is considered less secure than multi-factor authentication, we recommend that you set MaxAgeSessionSingleFactor property to a value that is equal to or lesser than the Multi-Factor Refresh Token Max Age property(MaxAgeSessionMultiFactor).
With that being said, this feature is being deprecated as described in the article you are following. So we do not recommend using this feature in new environments.
"After hearing from customers during the preview, we're planning to replace this functionality with a new feature in Azure Active Directory Conditional Access. Once the new feature is complete, this functionality will eventually be deprecated after a notification period. If you use the Configurable Token Lifetime policy, be prepared to switch to the new Conditional Access feature once it's available." from the article.

Microsoft Graph API - cannot refresh access token

I have registered a multitenant app at https://apps.dev.microsoft.com since the "admin consent" prompt wasn't available in the Azure AD apps. Admin consent is required for our app to retrieve info about users and their calendars.
I can provide admin consent from a completely different tenant than what this app is registered from and use the provided access token to retrieve all necessary information, however that obviously expires after an hour and we need offline access.
I have tried using the tenantId instead of 'common' in the https://login.windows.net/common/oauth2/token endpoint, however receive the same message as below.
The following is the data being submitted to the token endpoint in json format (converted within node to form encoded format before submitting):
{
grant_type: 'refresh_token',
client_id: 'e5c0d59d-b2c8-4916-99ac-3c06d942b3e3',
client_secret: '(redacted)',
refresh_token: '(redacted)',
scope: 'openid offline_access calendars.read user.read.all'
}
When I try to refresh the access token I receive an error:
{
"error":"invalid_grant",
"error_description":"AADSTS65001: The user or administrator has not consented to use the application with ID 'e5c0d59d-b2c8-4916-99ac-3c06d942b3e3'. Send an interactive authorization request for this user and resource.\r\nTrace ID: 2bffaa08-8c56-4872-8f9c-985417402e00\r\nCorrelation ID: c7653601-bf96-46c3-b1ff-4857fb25b7dc\r\nTimestamp: 2017-03-22 02:17:13Z",
"error_codes":[65001],
"timestamp":"2017-03-22 02:17:13Z",
"trace_id":"2bffaa08-8c56-4872-8f9c-985417402e00",
"correlation_id":"c7653601-bf96-46c3-b1ff-4857fb25b7dc"
}
This error occurs even when standard consent is used. I have also tried using the node-adal library instead of raw http requests which produces the exact same result.
I note that "offline_access" isn't a permission I am able to set within the MS apps portal, however I would guess the fact that I am getting a refresh token back means that I can refresh the access token?
For the record, the following is the node-adal code I used to see if I was doing something wrong:
var self = this;
var authenticationContext = new AuthenticationContext('https://login.windows.net/common');
authenticationContext.acquireTokenWithRefreshToken(
self.refreshToken,
self.clientId,
self.clientSecret,
'https://graph.microsoft.com/',
function(a) {
console.log(a);
}
);
Any help in getting this refresh process working is appreciated!
Please ensure that the tenant that you using for refreshing token is same as the tenant that you requesting for the access_token.
The refresh token request works well for me unless in the scenario of below:
register the app from protal using Microsoft account
user1 is in tenant1
add user1 as the external users to tenant2
request the access_token/refresh_token from tenant1(OK)
try to refresh the token using tenant1 in the request(OK)
try to refresh the token using tenant2 in the request(same error message)

Why is my request for a new access token not returning a new refresh token?

I am using the following code, along with my refresh token, to request a new access token:
exports.getTokenFromRefreshToken = function (user, callback) {
request.post({
url:'https://login.microsoftonline.com/12345678-1234-1234-1234-2f189712345/oauth2/token',
form: {
grant_type: 'refresh_token',
refresh_token: refresh_token,
client_id: client_id,
client_secret: client_secret,
resource: 'https://graph.microsoft.com'
}
}, function(err, httpResponse, body) {
if (!err) {
var tokens = JSON.parse(httpResponse.body);
console.log('getTokenFromRefreshToken() tokens = ' + JSON.stringify(tokens));
callback(null, tokens);
}
})
};
The httpResponse includes everything that I get when I make the original token request (from the code), but without a new refresh token. I was under the impression that I would also receive a new refresh token. Is that not the case?
You get a new refresh token only when you are including the offline_access scope.
ref.: https://azure.microsoft.com/en-us/documentation/articles/active-directory-v2-scopes/
The offline_access scope gives your app access to resources on behalf of the user for an extended time. On the work account consent page, this scope appears as the "Access your data anytime" permission. On the personal Microsoft account consent page, it appears as the "Access your info anytime" permission. When a user approves the offline_access scope, your app can receive refresh tokens from the v2.0 token endpoint. Refresh tokens are long-lived. Your app can get new access tokens as older ones expire.
Refresh tokens aren't refreshed the same way you can get a new access token using the refresh token. When a refresh token expires, you will need to need to get the credentials and do the initial token acquisition again.
More info here: Refreshing an Access Token
It looks like it should work except you seem to be missing the redirect URI. I have a working version of this call but it includes this redirect_uri. It produces for me both a new access and refresh token.
--
http://graph.microsoft.io/en-us/docs/authorization/app_authorization
Renew expiring access token using refresh token
The redirect URL that the browser is sent to when authentication is complete. This should match the redirect_uri value used in the first request.
I had the exact same issue, caused a headache for a while until the problem was found.
Seems like you are probably logging in with a Guest MS account (previously known as Live) and thus getting a 12hr expiry refresh token with no rolling window.
You need to use a full MS account to get the refresh token back in the response body (which is a token that will last 14 days), with a rolling window of 90 days.
For as long as you use a Guest MSA you will not get a refresh token back. Your code is correctly structured as far as I can see, and I don't believe you need a redirect_uri header as stated above, they are optional fields according to doco.
For more information on the types of refresh tokens, see this blog post:
Azure tokens

Resources