microsoft-authentication-library-for-js: Can't catch invalid_grant error after refresh token revoke

Core Library

MSAL.js v2 (@azure/msal-browser)

Core Library Version

2.14.1

Wrapper Library

Not Applicable

Wrapper Library Version

none

Description

We have an Azure AD B2C with custom policies configured. By our security policy we are going to revoke all refresh tokens once user changed his password. The expected behavior is like that;

  1. Revoke all refresh tokens for a particular user
  2. Access token is expired for that user
  3. User initiates any request which under the hood is calling acquireTokenSilent
  4. Token endpoint returns invalid_grant error
  5. We catch that error and sign the user out immediately.

Even though Msal.js v2 receives this error under the hood, it doesn’t allow developers to detect it and does a silent renew of the access/refresh token pair.

FYI: I’ve explored the source code a little bit and found that there’s no way to abandon silent request execution in the iframe in case of invalid_grant error. Does it mean that the new feature/flag is required? What do you think? Here is a reference to the code for your information: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/0e8a18c059bbacbd7f68057d1e760d9e52bdfa02/lib/msal-browser/src/app/ClientApplication.ts#L325

Error Message

Only network error with invalid_grant token endpoint error, which is correct.

Msal Logs

image

MSAL Configuration

Any relevant B2C config from this repository can be used.

Relevant Code Snippets

Any B2C example from this repository can be used

Reproduction Steps

  1. Sign in under any Azure AD B2C user.
  2. Wait for access token expiration (I used minimal access token’s expiration time = 5min).
  3. In Azure Portal click ‘Revoke sessions’ button for the user.
  4. In the web app put a breakpoint on catch clause of for PublicClientApplication.acquireTokenSilent
  5. Initiate a request in the web application which will cause a new token acquisition.

Problems:

  1. Error is not caught (execution flow won’t stop on the breakpoint).
  2. New refresh token/access token received successfully.

Expected Behavior

  1. I wanna be able to catch the invalid_grant (revoked/expired refresh token) exception and sign the user out immediately.

Generally, we are going to revoke all refresh tokens once user’s changed his password. That’s a part of our security policy.

Identity Provider

Azure B2C Custom Policy

Browsers Affected (Select all that apply)

Chrome, Safari

Regression

No response

Source

External (Customer)

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 1
  • Comments: 16 (3 by maintainers)

Most upvoted comments

I’m facing the same issue. In my case the function acquireTokenSilent returns undefined when the refresh token is expired. Below follows the log (for readability and privacy purposes, I removed some parts of text).

Description

  1. Log in through acquireTokenPopup
  2. Let the refresh token expires (usually after 24 hours)
  3. Call acquireTokenSilent, the promise is fulfilled but the authentication result is undefined

Expected behavior

The promise returned by acquireTokenSilent is rejected.

Actual behavior

It fulfills the promise returned by acquireTokenSilent with undefined.

The request logs instead shows the invalid_grant error with description AADSTS700084: The refresh token was issued to a single page app (SPA), and therefore has a fixed, limited lifetime of 1.00:00:00, which cannot be extended. It is now expired and a new sign in request must be sent by the SPA to the sign in page..

Logs

General

Request URL: https://login.microsoftonline.com/<TENANT-ID>/oauth2/v2.0/token
Request Method: POST
Status Code: 400 Bad Request
Remote Address: <REMOTE-ADDRESS>:443
Referrer Policy: strict-origin-when-cross-origin

Request Headers

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: it-IT,it;q=0.9,en;q=0.8,de;q=0.7,la;q=0.6,fr;q=0.5,es;q=0.4
Connection: keep-alive
Content-Length: 1808
content-type: application/x-www-form-urlencoded;charset=utf-8
Host: login.microsoftonline.com
Origin: <ORIGIN>
Referer: <REFER>
sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36

Response Header

Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Length,Content-Encoding
Cache-Control: no-store, no-cache
Content-Length: 740
Content-Type: application/json; charset=utf-8
Date: Mon, 07 Feb 2022 08:58:48 GMT
Expires: -1
nel: {"report_to":"network-errors","max_age":86400,"success_fraction":0.001,"failure_fraction":1.0}
P3P: CP="<CP>"
Pragma: no-cache
Referrer-Policy: strict-origin-when-cross-origin
report-to: {"group":"network-errors","max_age":86400,"endpoints":[{"url":"https://identity.nel.measure.office.net/api/report?catId=GW+estsfd+ams1"}]}
Set-Cookie: fpc=<FPC>; expires=Wed, 09-Mar-2022 08:58:48 GMT; path=/; secure; HttpOnly; SameSite=None
Set-Cookie: x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly
Set-Cookie: stsservicecookie=estsfd; path=/; secure; samesite=none; httponly
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
x-ms-clitelem: <TELEM>,
x-ms-ests-server: 2.1.12381.24 - NEULR1 ProdSlices
x-ms-request-id: <REQ-ID>

Request Payload

client_id: <CLIENT-ID>
scope: <SCOPE>
grant_type: refresh_token
client_info: 1
x-client-SKU: msal.js.browser
x-client-VER: 2.21.0
x-client-OS: 
x-client-CPU: 
x-ms-lib-capability: retry-after, h429
x-client-current-telemetry: 5|61,0,,,|,
x-client-last-telemetry: <LAST-TELEMETRY>
client-request-id: <CLIENT-REQUEST-ID>
refresh_token: <REFRESH-TOKEN>
X-AnchorMailbox: <ANCHOR-MAILBOX>

Response

{
  correlation_id: <CORRELATION-ID>
  error: "invalid_grant"
  error_codes: [700084]
  error_description: "AADSTS700084: The refresh token was issued to a single page app (SPA), and therefore has a fixed, limited lifetime of 1.00:00:00, which cannot be extended. It is now expired and a new sign in request must be sent by the SPA to the sign in page. The token was issued on 2022-02-04T14:02:43.2682473Z.\r\nTrace ID: <TRACE-ID>\r\nCorrelation ID: <CORRELATION-ID>\r\nTimestamp: 2022-02-07 08:58:48Z"
  error_uri: "https://login.microsoftonline.com/error?code=700084"
  suberror: "bad_token"
  timestamp: "2022-02-07 08:58:48Z"
  trace_id: <TRACE-ID>
}

The library receiving a 400 error from the token endpoint when calling acquireTokenSilent is expected and by design. This error should be caught and handled by the library, which will either result in the tokens successfully renewed, or in an interaction required error (if the library is unable to silently renew the refresh token, i.e. the user no longer an active session or third-party cookies are blocked in their browser). Unfortunately, even though we are catching the 400 error, it will still get printed in the console, but that can be ignored (unless if your application is receiving the 400 error in your error handler). Please confirm whether or not your error handler is receiving this error (as opposed to having it just be printed to the console).

I wanna be able to catch the invalid_grant (revoked/expired refresh token) exception and sign the user out immediately.

@Codeluck This would be an enhancement that we do not currently support. You can potentially get the behavior you desire by providing a custom implementation of INetworkModule, where you write logic to detect this scenario and throw a new error that isn’t caught by the library.

Links:

We are facing the same issue as well, I’m unable to catch the exception in acquireTokenSilent subscription block with instance of InteractionRequiredAuthError