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

查看:51
本文介绍了在 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 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 can I 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.

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

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ël Derriey 的帮助和指导(请务必查看他对此解决方案的上下文中的更多信息).这是我想出的解决方案,它对我有用:

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

options.Events = new CookieAuthenticationEvents
{
    OnValidatePrincipal = context =>
    {
        //check to see if user is authenticated first
        if (context.Principal.Identity.IsAuthenticated)
        {
            //get the user's 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 中.

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 中,context 变量的类型为 CookieValidatePrincipalContext,有一个名为 ShouldRenew.将该属性设置为 true 会指示中间件发出新的 cookie.

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.

如果您无法续订令牌或发现刷新令牌已过期并且您想阻止用户继续前进,则同一类具有 RejectPrincipal 方法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.

我有一些代码展示了如何在 mderriey/TokenRenewal GitHub 上的存储库.虽然意图不同,但它展示了如何使用这些事件的机制.

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天全站免登陆