在ASP.NET Core中处理过期的刷新令牌 [英] Handling Expired Refresh Tokens in ASP.NET Core

查看:222
本文介绍了在ASP.NET Core中处理过期的刷新令牌的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请参阅下面的代码,以解决此问题

我正在尝试找到最佳和最有效的方法来处理ASP.NET Core 2.1中已过期的刷新令牌.

I'm trying to find the best and most efficient way to deal with a refresh token that has expired within ASP.NET Core 2.1.

让我解释一下.

我正在使用OAUTH2和OIDC来请求授权码授予流(或与OIDC的混合流).这种流/授予类型使我可以访问AccessToken和RefreshToken(也包括授权码,但这不是针对此问题).

I am using OAUTH2 and OIDC to request Authorization Code grant flows (or Hybrid flow with OIDC). This flow/grant type gives me access to an AccessToken, and a RefreshToken (Authorization Code as well, but that is not for this question).

访问令牌和刷新令牌由ASP.NET核心存储,可以分别使用HttpContext.GetTokenAsync("access_token");HttpContext.GetTokenAsync("refresh_token");进行检索.

The access token and refresh token are stored by ASP.NET core, and can be retrieved using HttpContext.GetTokenAsync("access_token"); and HttpContext.GetTokenAsync("refresh_token"); respectively.

我可以毫无问题地刷新access_token.当refresh_token过期,被吊销或以某种方式无效时,该问题就起作用了.

I can refresh the access_token without any issues. The issue comes into play when the refresh_token is expired, revoked or invalid in some way.

正确的流程是让用户再次登录,然后再次返回整个身份验证流程.然后,应用程序将返回一组新的令牌.

The correct flow would be to have the user log in again and go back though the entire authentication flow again. Then the application gets a new set of tokens returned.

我的问题是如何以最佳和最正确的方法实现这一目标.我决定编写一个自定义中间件,如果access_token已过期,则尝试对其进行续订.然后,中间件将新令牌设置为HttpContext的AuthenticationProperties中,以便以后在管道中进行的任何调用都可以使用它.

My question is how to achieve this in the best and most correct method. I decided to write a custom middleware that attempts to renew the access_token if it has expired. The middleware then sets the new token into the AuthenticationProperties for the HttpContext so it can be used by any calls later down the pipe.

如果由于任何原因刷新令牌失败,我需要再次调用ChallengeAsync.我正在从中间件调用ChallengeAsync.

If refreshing the token fails for any reason, I need to call ChallengeAsync again. I am calling ChallengeAsync from the middleware.

这是我遇到一些有趣行为的地方.但是,在大多数情况下,这是可行的,但是有时我会收到500个错误,而对于失败的原因却没有任何有用的信息.似乎中间件在尝试从中间件调用ChallengeAsync时遇到问题,也许另一个中间件也在尝试访问上下文.

This is where I am running into some interesting behavior. Most of the time this works, however, sometimes I'll get 500 errors with no helpful information as to what is failing. It almost seems like the middleware is having issues trying to call ChallengeAsync from the middleware, and maybe another middleware is also trying to access the context.

我不太确定发生了什么.我不太确定这是否是放置此逻辑的正确位置.也许我不应该在中间件中使用它,也许在其他地方.也许Polly是HttpClient的最佳选择.

I'm not quite sure what is going on. I'm not quite sure if this is the right place to put this logic or not. Maybe I should not have this in middleware, maybe somewhere else. Maybe Polly for the HttpClient is the best place.

我愿意接受任何想法.

感谢您可以提供的任何帮助.

Thanks for any help you can provide.

最适合我的代码解决方案

感谢MickaëlDerriey 的帮助和指导(请务必查看他的回答)有关此解决方案的更多信息)这是我想出的解决方案,它对我有用:

Thanks to Mickaël Derriey for the help and direction (be sure to see his answer for more information to the context of this solve) This is the solution that I've come up with and it working for me:

options.Events = new CookieAuthenticationEvents
{
    OnValidatePrincipal = context =>
    {
        //check to see if user is authenticated first
        if (context.Principal.Identity.IsAuthenticated)
        {
            //get the users tokens
            var tokens = context.Properties.GetTokens();
            var refreshToken = tokens.FirstOrDefault(t => t.Name == "refresh_token");
            var accessToken = tokens.FirstOrDefault(t => t.Name == "access_token");
            var exp = tokens.FirstOrDefault(t => t.Name == "expires_at");
            var expires = DateTime.Parse(exp.Value);
            //check to see if the token has expired
            if (expires < DateTime.Now)
            {
                //token is expired, let's attempt to renew
                var tokenEndpoint = "https://token.endpoint.server";
                var tokenClient = new TokenClient(tokenEndpoint, clientId, clientSecret);
                var tokenResponse = tokenClient.RequestRefreshTokenAsync(refreshToken.Value).Result;
                //check for error while renewing - any error will trigger a new login.
                if (tokenResponse.IsError)
                {
                    //reject Principal
                    context.RejectPrincipal();
                    return Task.CompletedTask;
                }
                //set new token values
                refreshToken.Value = tokenResponse.RefreshToken;
                accessToken.Value = tokenResponse.AccessToken;
                //set new expiration date
                var newExpires = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResponse.ExpiresIn);
                exp.Value = newExpires.ToString("o", CultureInfo.InvariantCulture);
                //set tokens in auth properties 
                context.Properties.StoreTokens(tokens);
                //trigger context to renew cookie with new token values
                context.ShouldRenew = true;
                return Task.CompletedTask;
            }
        }
        return Task.CompletedTask;
    }
};

推荐答案

访问令牌和刷新令牌由ASP.NET核心存储

The access token and refresh token are stored by ASP.NET core

我认为必须注意,令牌存储在cookie中,该cookie标识了您的应用程序的用户.

I think it's important to note that the tokens are stored in the cookie that identifies the user to your application.

现在这是我的看法,但是我认为自定义中间件不是刷新令牌的正确位置. 这样做的原因是,如果成功刷新令牌,则需要替换现有的令牌,并将其以新的Cookie的形式发送回浏览器,以替换现有的令牌.

Now this is my opinion, but I don't think a custom middleware is the right place to refresh tokens. The reason for this is that if you successfully refresh the token, you'll need to replace the existing one and send it back to the browser, in the form of a new cookie that will replace the existing one.

这就是为什么我认为最相关的地方是当ASP.NET Core正在读取cookie时.每种身份验证机制都会公开多个事件.对于Cookie,有一个名为ValidatePrincipal的名称,它在读取Cookie并成功从其反序列化后的每个请求中都会被调用.

This is why I think the most relevant place to do this is when the cookie is being read by ASP.NET Core. Every authentication mechanism exposes several events; for cookies, there's one called ValidatePrincipal which is called on every request after the cookie has been read and an identity has succesfully been deserialized from it.

public void ConfigureServices(ServiceCollection services)
{
    services
        .AddAuthentication()
        .AddCookies(new CookieAuthenticationOptions
        {
            Events = new CookieAuthenticationEvents
            {
                OnValidatePrincipal = context =>
                {
                    // context.Principal gives you access to the logged-in user
                    // context.Properties.GetTokens() gives you access to all the tokens

                    return Task.CompletedTask;
                }
            }
        });
}

这种方法的好处是,如果您设法更新令牌并将其存储在AuthenticationProperties中,则类型为CookieValidatePrincipalContextcontext变量具有名为

The nice thing about this approach is that if you manage to renew the token and store it in the AuthenticationProperties, the context variable which is of type CookieValidatePrincipalContext, has a property called ShouldRenew. Setting that property to true instructs the middleware to issue a new cookie.

如果您无法续签令牌,或者您发现刷新令牌已过期,并且想要阻止用户继续前进,则该类具有

If you can't renew the token or you find the refresh token is expired and you want to prevent the user from going forward, that same class has a RejectPrincipal method which instructs the cookie middleware to treat the request as if it was aonymous.

这样做的好处是,如果您的MVC应用仅允许经过身份验证的用户访问,则MVC将负责发出HTTP 401响应,身份验证系统将捕获该响应并将其转换为质询,并重定向用户回到身份提供商.

The nice thing about this is that if your MVC app only allows authenticated users to access it, MVC will take care of issuing the HTTP 401 response which the authentication system will catch and turn into a Challenge and the user will be redirected back to the Identity Provider.

我有一些代码说明如何在GitHub上的 mderriey/TokenRenewal 存储库中工作.尽管目的不同,但它说明了如何使用这些事件的机制.

I have some code that shows how this would work over at the mderriey/TokenRenewal repository on GitHub. While the intent is different, it shows the mechanics of how to use these events.

这篇关于在ASP.NET Core中处理过期的刷新令牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆