Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MSAL on Blazor WebAssembly refresh token expiration invalid_grant regression #48264

Closed
1 task done
dino182 opened this issue May 16, 2023 · 25 comments
Closed
1 task done
Assignees
Labels
area-blazor Includes: Blazor, Razor Components feature-blazor-msal This issue is related to MSAL usage in Blazor investigate Needs: Repro Indicates that the team needs a repro project to continue the investigation on this issue

Comments

@dino182
Copy link

dino182 commented May 16, 2023

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

The fix for #28151 in #28498 appears to have been regressed by the c1d16e3#diff-e93d88646a9c7b8accad9fd0d381af964d3f5e6bba9d4e72c22f602565a3c4cc commit. This impacts the 7.x versions of the Microsoft.Authentication.WebAssembly.Msal. The 6.x versions still contain the previous fix.
The bug is exactly as described in #28151. The only difference is that the error message returned from the failed POST request to the token endpoint is:

"error":"invalid_grant"
"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 2023-05-15T08:46:39.6945713Z.\r\nTrace ID: 002abb82-c8d5-4519-8340-3b1f3bc70000\r\nCorrelation ID: cda6cada-7268-4328-9ae6-4c7dac1d8b54\r\nTimestamp: 2023-05-16 19:43:43Z"

Expected Behavior

The AuthenticationService should handle this exception and automatically trigger a new sign in request. It should not result in an unhandled error bubbling up to the Blazor app.

Steps To Reproduce

  • Create a typical Blazor + MSAL application using local storage as the token cache
  • Trigger a sign in and extract the tokens from the developer tools window
  • Wait 24 hours to allow the refresh token to expire
  • Paste the extracted tokens back into local storage if these are replaced
  • Restart the application

Exceptions (if any)

No response

.NET Version

7.0.203

Anything else?

.NET SDK:
 Version:   7.0.203
 Commit:    5b005c19f5

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.22621
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\7.0.203\

Host:
  Version:      7.0.5
  Architecture: x64
  Commit:       8042d61b17

.NET SDKs installed:
  6.0.202 [C:\Program Files\dotnet\sdk]
  7.0.203 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.16 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 7.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.8 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.14 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.15 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.16 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 7.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 6.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 6.0.8 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 6.0.12 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 6.0.16 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 7.0.5 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
  x86   [C:\Program Files (x86)\dotnet]
    registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables:
  Not set

global.json file:
  Not found

Learn more:
  https://aka.ms/dotnet/info

Download .NET:
  https://aka.ms/dotnet/download
@dotnet-issue-labeler dotnet-issue-labeler bot added the area-blazor Includes: Blazor, Razor Components label May 16, 2023
@mkArtakMSFT mkArtakMSFT added this to the .NET 8 Planning milestone May 17, 2023
@ghost
Copy link

ghost commented May 17, 2023

To learn more about what this message means, what to expect next, and how this issue will be handled you can read our Triage Process document.
We're moving this issue to the .NET 8 Planning milestone for future evaluation / consideration. Because it's not immediately obvious what is causing this behavior, we would like to keep this around to collect more feedback, which can later help us determine how to handle this. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact work.

@mkArtakMSFT mkArtakMSFT added the feature-blazor-msal This issue is related to MSAL usage in Blazor label May 17, 2023
@robaszym
Copy link

We started to get the same issue recently after updating some libraries (.NET from 7.0.5 to 7.0.8) and Microsoft.Identity.Web from 2.11.1 to 2.12.4 (but it's rather related to .NET upgrade only). In 7.0.5 version new tokens were requested automatically (MS sign in page was visible for a moment) in 7.0.8 it ends up with error from the description.
App is Blazor WASM (hosted) and uses MSAL.

@jackpercy-acl
Copy link

We're having the same problem in Microsoft.Authentication.WebAssembly.Msal 7.0.0.

We see the mentioned AADSTS700084 error for the expired token as well as AADSTS50078 for an MFA expiry.

The try catch in the getUser() method was removed in this commit: 3ac7ee7#diff-e93d88646a9c7b8accad9fd0d381af964d3f5e6bba9d4e72c22f602565a3c4ccL103

@jornjanssen90
Copy link

Same problem in Microsoft.Authentication.WebAssembly.Msal 7.0.10

@ghost
Copy link

ghost commented Oct 4, 2023

To learn more about what this message means, what to expect next, and how this issue will be handled you can read our Triage Process document.
We're moving this issue to the .NET 9 Planning milestone for future evaluation / consideration. Because it's not immediately obvious what is causing this behavior, we would like to keep this around to collect more feedback, which can later help us determine how to handle this. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact work.

@jornjanssen90
Copy link

Dont understand that this is being moved to .NET 9. This is a crucial feature for Blazor with MS authentication. I can't upgrade any of my projects because of this. Just restore it to how it was implemented in .NET 6 @javiercn

@pastidev
Copy link

Is there any workaround for this, how can we catch the exception and redirect to azure login?

@MackinnonBuck MackinnonBuck added Pillar: Technical Debt bug This issue describes a behavior which is not expected - a bug. and removed investigate labels Dec 4, 2023
@mkArtakMSFT mkArtakMSFT modified the milestones: Planning: WebUI, 8.0.x Dec 14, 2023
@luca-trifilio
Copy link

Hi. We have a couple of web applications that used to work seamlessly authentication side, using the standard MSAL mechanism. Originally, both apps were developed using .NET 6. Recently we upgraded to .NET 8 and consequently we upgraded all the libraries, and there started the issue described here.
At the moment, we have a strange behavior where there are users with no issues, while others (myself too) are experiencing the same issue as other colleagues over here: they already have access token in the browser local storage, but it's expired and the mechanism of refresh doesn't work anymore.
So they are stuck, and in the console we can see an error saying that the API call to get the token failed. This is the response:

{
"error": "invalid_grant",
"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 2023-12-18T09:49:15.0104937Z. Trace ID: 56783d5b-15d1-4dc8-be60-5caf27519200 Correlation ID: 5b7629f9-e56e-4965-8d32-04517f4eeea5 Timestamp: 2023-12-19 14:55:27Z",
"error_codes": [
700084
],
"timestamp": "2023-12-19 14:55:27Z",
"trace_id": "56783d5b-15d1-4dc8-be60-5caf27519200",
"correlation_id": "5b7629f9-e56e-4965-8d32-04517f4eeea5",
"error_uri": "https://login.microsoftonline.com/error?code=700084",
"suberror": "bad_token"
}

Any suggestion to get it fixed?

@mkArtakMSFT
Copy link
Member

We'll consider patching this for both 7.0.x and 8.0.x.
Let's start from investigating this first. @halter73 it's all yours. Thanks!

@pk-hoh
Copy link

pk-hoh commented Jan 11, 2024

Is there any temporary workaround we can apply? Like, catch the exception and manually trigger a sign-in request? We aggressively expire refresh tokens, so this breaks our apps.

@brandonseydel
Copy link

I need this too. Cannot ship any application with this error or at least a sufficient work around.

@crookm
Copy link

crookm commented Jan 24, 2024

Had this while upgrading projects to .NET 8 from 6 (with the MS packages tracking the framework version).

Workaround for me was to revert the packages Microsoft.Authentication.WebAssembly.Msal and Microsoft.AspNetCore.Components.WebAssembly.Authentication to their 6.x (6.0.26) versions until this can be resolved in later versions. Not 100% sure if both packages needed to be reverted, but I still had some issues when I just reverted MSAL.

@dotnet-policy-service dotnet-policy-service bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@wtgodbe wtgodbe removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@dotnet-policy-service dotnet-policy-service bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@dotnet dotnet deleted a comment from dotnet-policy-service bot Feb 13, 2024
@ShawnTheBeachy
Copy link

So, is anything actually happening on this? This is a major issue for us as well. Shipping is a no-go with this bug, and rolling packages back two major versions hurts.

@halter73 halter73 removed the bug This issue describes a behavior which is not expected - a bug. label Mar 7, 2024
@halter73 halter73 removed this from the 8.0.x milestone Mar 7, 2024
@halter73
Copy link
Member

halter73 commented Mar 7, 2024

A failed POST to the /token endpoint producing a 400 status with a AADSTS700084 error in the response is expected in some circumstances. You should not be alarmed to see this in your network logs.

Refresh tokens have a 24 hour lifetime. Once expired MSAL will attempt to silently acquire a a new auth code and then redeem for a fresh set of access, id and refresh tokens. This fallback fails in Safari as it depends on 3P cookies which are blocked by default. 3P cookies are not blocked by default in Chrome, yet, which is why it may succeed there.

Given that silent calls are not ever guaranteed you should always have a backup plan, e.g. invoke acquireTokenRedirect or acquireTokenPopup in the event the silent call fails.

AzureAD/microsoft-authentication-library-for-js#6765 (comment)
AzureAD/microsoft-authentication-library-for-js#6830

Another reason the token refresh might fail is that the server has configured strict Content Security Policies (CSP) blocking the silent iframe-based login.

Expected Behavior

The AuthenticationService should handle this exception and automatically trigger a new sign in request. It should not result in an unhandled error bubbling up to the Blazor app.

It may have worked this way previously, but this is not the documented behavior. In circumstances where silent token acquisition fails, you are expected to handle the interactive login redirect yourself rather than expect Blazor to automatically redirect for you and possibly unload your application at an unexpected time.

If you are using IAccessTokenProvider.RequestAccessToken() directly, this means you should check if accessTokenResult.Status == AccessTokenResultStatus.RequiresRedirect. If it does, you should call NavigationManager.NavigateToLogin(accessTokenResult.InteractiveRequestUrl) if want to immediately redirect to login.

If you're getting an AccessTokenNotAvailableException from something like the AuthorizationMessageHandler, you're expected to call AccessTokenNotAvailableException.Redirect()

Can anyone provide a sample where the where the AccessTokenResultStatus is incorrect? Or where either AccessTokenNotAvailableException.Redirect() or NavigationManager.NavigateToLogin(accessTokenResult.InteractiveRequestUrl) does not work?

@halter73 halter73 added Needs: Repro Indicates that the team needs a repro project to continue the investigation on this issue and removed Pillar: Technical Debt labels Mar 7, 2024
@halter73 halter73 removed their assignment Mar 7, 2024
@dotnet-policy-service dotnet-policy-service bot added the Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. label Mar 7, 2024
@dino182
Copy link
Author

dino182 commented Mar 18, 2024

I have created a sample repo https://github.com/dino182/blazor-msal-expired-refresh-token to try to recreate the original behaviour I experienced when I opened this issue.

At the time, when the error was encountered, I'm sure the application displayed the unhandled exception Blazor error UI panel, logged the error in the console and stopped processing. If I recall correctly, refreshing the browser resulted in the same behaviour, so the application was effectively unusable from a user's perspective.

However, I cannot reproduce this behaviour now. This repo demonstrates the desired behaviour (in .NET6, .NET7 and .NET8), where the error is logged in the console and something (I don't know what) is causing the sign in request to be automatically retried. Perhaps other framework changes have been introduced since this issue was opened that have fixed the poor behaviour. Whatever has happened, my use-case seems to be working fine and I can't break it now.

I have moved away from MSAL.js and am using same site cookie authentication instead. So, personally, I am happy for this to be closed.

If anyone else still has a problem with this and can respond to @halter73's request please feel free to chip in:

Can anyone provide a sample where the where the AccessTokenResultStatus is incorrect? Or where either AccessTokenNotAvailableException.Redirect() or NavigationManager.NavigateToLogin(accessTokenResult.InteractiveRequestUrl) does not work?

If not, I'll just leave this issue to be closed automatically.

@dotnet-policy-service dotnet-policy-service bot added Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update. and removed Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. Status: No Recent Activity labels Mar 18, 2024
@chulla
Copy link

chulla commented Mar 18, 2024

I have the same problem.

I have this Ihttpclientfactory DelegatingHandler:

   protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   {
       var accessTokenResult = await _accessor.TokenProvider.RequestAccessToken();
    
       if (accessTokenResult.TryGetToken(out var accessToken) && !String.IsNullOrWhiteSpace(accessToken.Value))
       {
           request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Value);
       }
       else if (accessTokenResult.Status == AccessTokenResultStatus.RequiresRedirect)
       {
           _navigationManager.NavigateToLogin(accessTokenResult.InteractiveRequestUrl);
         // return new HttpResponseMessage(HttpStatusCode.Unauthorized);
       }
....
}

If the user logs in and gets a token... EVERYTHING OK!

accessTokenResult has a valid token.

image

image

But, if I have the user was already logged in for 24h, with a ClaimsPrincipal but expired, then it does not refresh the token.. showing 401 error in the call to an API with JWT

accessTokenResult is null

image

@mkArtakMSFT mkArtakMSFT added investigate and removed Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update. labels Mar 19, 2024
@chulla
Copy link

chulla commented Mar 23, 2024

I'm debugging the exception and I find that when I try to get a resfresh-token I get this:

The error :

image

The debug :

image

I have also debugged the POST call in postman :

image

@chulla
Copy link

chulla commented Mar 26, 2024

I do not understand your answer @jornjanssen90 .

The real problem in this ticket is that MSAL, once the user is already logged in +24 hours, having a valid authorization and an expired token, cannot request a new token with the access token he had.. giving error 400 in POST on the refresh token request

as you can see here :
image

@halter73
Copy link
Member

halter73 commented Apr 5, 2024

The real problem in this ticket is that MSAL, once the user is already logged in +24 hours, having a valid authorization and an expired token, cannot request a new token with the access token he had.. giving error 400 in POST on the refresh token request

As I mentioned in #48264 (comment), this is the expected behavior.

Can anyone provide a sample where the where the AccessTokenResultStatus is incorrect? Or where either AccessTokenNotAvailableException.Redirect() or NavigationManager.NavigateToLogin(accessTokenResult.InteractiveRequestUrl) does not work?

It's been a month since I've asked this, and I haven't seen a sample where this isn't the case. @chulla Has mentioned that there's an issue with their custom DelegatingHandler, but the SendAsync method still attempts the request without an access token which naturally results in a 401 response. The call to NavigationManager.NavigateToLogin does not stop the execution of SendAsync unless it's called during static rendering in which case it throws, and Blazor WebAssembly is not statically rendering.

The built-in AuthorizationMessageHandler throws an exception before attempting any request if it fails to obtain an access token. This exception should be handled by the calling component as you can see demonstrated in my repro project.

Nonetheless, I updated my repro project to implement a custom DelegatingHandler using the SendAsync implementation from #48264 (comment), and other than some noise in the browser network and console tabs from the uncaught exception and the unnecessary request to /WeatherForecast, everything works fine. The app redirects to authentication/login and ultimately login.microsoftonline.com after getting a 400 response from the attempted POST to the /token endpoint in an unsuccessful attempt to use the expired refresh token.

You can take look at the updated repro project here: https://github.com/halter73/BlazorHostedWasmAAD/compare/custom-delegating-handler

browser tools network tab
browser console

If someone can provide a full repro project on GitHub where the AccessTokenResultStatus is incorrect, or where AccessTokenNotAvailableException.Redirect()/NavigationManager.NavigateToLogin(accessTokenResult.InteractiveRequestUrl) does not work, please file a new issue with a link to the repro.

@halter73 halter73 closed this as completed Apr 5, 2024
@ShawnTheBeachy
Copy link

ShawnTheBeachy commented Apr 5, 2024

@halter73 OK so for clarity, are you saying that the AuthorizeView Blazor component should render the Authorized section, even if the refresh token is expired? Because it seemed to me that it was failing to do so. Which means I never even get to the point of a DelegatingHandler receiving an exception. But maybe I was mistaken and it is passing through; it's a bit hard to test, needing to wait 24 hours between application startups.

EDIT: It looks like the AuthorizeView component is not a culprit like I thought. Adding a redirect in my handler as below seems to have fixed the issue.

As a sidenote for anyone coming across this in the future who, like me, doesn't want to have to do a try/catch/redirect on every single API request like in the provided sample, I believe something like this should work inside your handler which inherits AuthorizationMessageHandler:

protected override async Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request,
    CancellationToken cancellationToken
)
{
    try
    {
        return await base.SendAsync(request, cancellationToken);
    }
    catch (AccessTokenNotAvailableException tokenException)
    {
        tokenException.Redirect();
        return new HttpResponseMessage { StatusCode = HttpStatusCode.Unauthorized };
    }
}

@hjrb
Copy link

hjrb commented Jun 26, 2024

They blinded us with science. Result: no resolution. Just some workarounds every developer has to implement himself. Users will be bothered will a full refresh potentially loosing data and for sure context. Feels like the old days with sessions. If that is the best, the best won't do.

@kiranchandran
Copy link

Hi I agree 100% with @hjrb

Hi @halter73 Its very un-fortunate that you people are not having a solution even after an year. Please show this issue to someone who developed this AuthenticationService.ts, they can fix this quickly.

I understand that browser blocks the 3rd party cookie and the silent token renew fails. so what is the ideal solutions?
We need a configuration in this js pluggin so that we can disable the silent renewal of token which is the issue here. Or if the silent renewal fails you have to automatically do the full redirect for getting the new token. As a framework I would except this to be done by the AuthenticationService.ts rather than each developer doing their own fix for the same problem.

@halter73 Instead of closing the issue blindly please review this issue correctly and give us a proper fix and we are waiting for more than an year and please be responsible to the community. Please re-open this issue

@suntaurus
Copy link

I am also getting the same error. I am using v8.0.8 with blazor wsam.
No idea why this is closed? has anyone got any workaround

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 2024-09-12T12:42:36.3844597Z. Trace ID: 034cf4b8-f420-4aec-a924-4fd7a2d64301 Correlation ID: dbdac564-39c4-45d8-9ab6-a6c66b83f520 Timestamp: 2024-09-16 07:09:19Z

@jornjanssen90
Copy link

jornjanssen90 commented Sep 17, 2024

@suntaurus you have to do the redirect yourself now. i have made a 'FetchService' class where i use the httpclient with my own exceptionhandling. Here is an example of my Get function:

public async Task Get(string endpoint)
{
    try
    {
        var response = await _httpClient.GetAsync(endpoint);
        if (!response.IsSuccessStatusCode)
        {
            await HandleError(response);
        }
    }
    catch (AccessTokenNotAvailableException ex)
    {
        ex.Redirect();
    }
}

@suntaurus
Copy link

I have added to the AuthorizationHandler as mentioned by @ShawnTheBeachy. I need to check whether this fixes it.

public class AuthorizationHandler : AuthorizationMessageHandler
{
protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
return base.SendAsync(request, cancellationToken);
}
catch (AccessTokenNotAvailableException ax)
{
ax.Redirect();
return Task.FromResult(new HttpResponseMessage { StatusCode = HttpStatusCode.Unauthorized });
}
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components feature-blazor-msal This issue is related to MSAL usage in Blazor investigate Needs: Repro Indicates that the team needs a repro project to continue the investigation on this issue
Projects
None yet
Development

No branches or pull requests