dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.23k stars 9.95k forks source link

JWT token silent renew breaks and causes crash on React template with authentication #17962

Closed azydevelopment closed 7 months ago

azydevelopment commented 4 years ago

Describe the bug

The JWT silent renew in the React template with authentication can fail in some scenarios. What happens is the token is listed as expired (user.expires_in is negative and outside the 5 minute clock skew grace period). As a result, the API request fails with a 401, the token is never renewed, and the user can no longer get into [Authorize] marked areas. The only way to solve this I found (and the workaround isn't even confirmed), is to explicitly press the logout button and restart my application (presumably to reset authentication/authorization on the client side SPA).

If you don't explicitly log out, this issue will persist even on restarts of the ASP.NET application presumably because it's a client side issue. There are a lot of steps to the below so some may or may not actually contribute to the issue but it is a consistent repro.

To Reproduce

    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(opt =>
    {
        foreach (var c in opt.Clients)
            c.AccessTokenLifetime = 65;
    });

Expected outcome: You get to the Fetch Data page with a renewed JWT. Actual outcome: A crash with a JWT that hasn't and apparently won't renew.

image

You can break into AuthorizeService.js and inspect the user value to see that the token is expired. You can actually see the token work and count more and more negative until it gets to 300 at which point it'll then fail.:

image

Further technical details

.NET Core SDK (reflecting any global.json): Version: 3.0.100 Commit: 04339c3a26

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

Host (useful for support): Version: 3.0.0 Commit: 7d57652f33

.NET Core SDKs installed: 3.0.100 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed: Microsoft.AspNetCore.All 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

To install additional .NET Core runtimes or SDKs: https://aka.ms/dotnet-download

Tratcher commented 4 years ago

@javiercn this looks like an issue with the SPA template.

berik commented 4 years ago

If this issue will be fixed then issue in docs will be resolved.

Also, in the past similar issues were raised 1

pranavkm commented 4 years ago

@javiercn could you have a look?

azydevelopment commented 4 years ago

FYI. If this turns out to be a problem, it seems like it'd be a pretty big one. This would mean that if a user closes a browser and walks away, comes back after the token expires, that they'll get undefined behaviour when the next API call occurs.

I'm hoping I'm missing something and there's actually no issue here or at least a reasonable workaround.

bendandoSolo commented 4 years ago

I have been having a similar issue with my project, after an hour we found that the client app was authenticated but the backend (api controller) was no longer authenticated. To get round this we displayed an error message advising users to logout and login again.

azydevelopment commented 4 years ago

Yeah that's the behaviour I get. The client app lets me into JSX defined AuthorizeRoute paths but when an API call is made, a 401 is returned and the client app doesn't know what to do with it.

Having a user log out and log in again does not seem like a valid solution to me and just doesn't match the expected UX of an app with authentication/authorization and a 'Remember Me' checkbox.

azydevelopment commented 4 years ago

Is there a workaround for this in the meantime? Also, maybe a stupid question: When it says 5.0.0-preview1, does that mean it would only be available in aspnetcore version >5?

If so, why not 4.0? As of right now, this seems like a blocker to ship any production code based on the React + Identity template.

cash-cash commented 4 years ago

You can work around it quickly in the meantime by triggering a silent sign in for your user when you receive an expired token.

While delay isn't ideal, it does stop the site crashing.

async getAccessToken() {
    await this.ensureUserManagerInitialized()
    const user = await this.userManager.getUser()
    if (user.expired === true) {
      try {
        //Attempt to sign in our user silently
        const silentUser = await this.userManager.signinSilent(this.createArguments(LoginMode.Silent))
        //Return refreshed access token
        return silentUser && silentUser.access_token
      } catch (err) {
        //Failed to sign in user silently
        //Sign out user or user other authentication method
      }
    }
    return user && user.access_token
}

Unfortunately when getting our user we don't appear to get the refresh token. Otherwise I'd look at modifying startup.cs, and creating a new tokenController as per https://www.blinkingcaret.com/2018/05/30/refresh-tokens-in-asp-net-core-web-api/

vanbukin commented 4 years ago

@azydevelopment Pure SPA's are dead now https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more

I think you should build BFF application and use Cookies instead issuing tokens via front-channel communication (check Developer Guidance, Option 1 at link).

This is the new reality. Chrome will also implement that privacy enhancements in 2022.

akonyer commented 4 years ago

I am experiencing this same problem.

My authorization token is expired, but the component still lets me through. When I hit an API endpoint with Authorization I receive a 401. I am using the boilerplate code from the "dotnet new react -o -au Individual" command.

azydevelopment commented 4 years ago

Hey @javiercn

Any word on this one? Seems like a fundamental requirement for even a basic web app.

Btw, I should be clear, all the stuff coming out of the aspnetcore team is great.

bugzyUK commented 4 years ago

Hey, I am also experiencing the same issue.

Stupidly I didn't think this would be a big problem to solve (on my end) and my site has already gone live and in use. After struggling to resolve it I found this thread and now realised the issue is much bigger than me. Would love to hear where this is at.

akonyer commented 4 years ago

I am using cash-cash's workaround for now. It's not perfect, but it does work.

mkArtakMSFT commented 4 years ago

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

azydevelopment commented 4 years ago

Just in the interest of understanding what we're looking at here, is this likely to make it in the release after your coming release? We're trying to figure out as an early binding decision on whether to use aspnetcore identity and it's difficult if the default framework behaviour is that after a timeout, every user is stuck in an undefined state.

I also read the Triage Process and it looks like this should be marked as a bug maybe?

dougweems commented 4 years ago

Where do I find updates on this issue?

azydevelopment commented 3 years ago

Thanks for the consistent updates. Is this going to be fixed any time soon? I see the workaround but doesn't bring it up to parity with other basic identity solutions in the wild. I really like .NET Core and would like to stick as close to the full stack you guys have put together.

ghost commented 1 year ago

Thanks for contacting us. We're moving this issue to the .NET 8 Planning milestone for future evaluation / consideration. Because it's not immediately obvious that this is a bug in our framework, we would like to keep this around to collect more feedback, which can later help us determine the impact of it. 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 issues. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

ghost commented 11 months ago

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.

javiercn commented 7 months ago

@mkArtakMSFT closing this as its related to auth in the spa templates and not in 8.0