web-dev-qa-db-ja.com

ASP.NET Coreでの期限切れの更新トークンの処理

この問題を解決したコードについては以下を参照してください

ASP.NET Core 2.1内で期限切れになった更新トークンを処理するための最良かつ最も効率的な方法を見つけようとしています。

もう少し説明させてください。

OAUTH2とOIDCを使用して、承認コード付与フロー(またはOIDCとのハイブリッドフロー)を要求しています。このフロー/付与タイプにより、AccessTokenおよびRefreshTokenへのアクセスが許可されます(認証コードも同様ですが、これはこの質問のためではありません)。

アクセストークンとリフレッシュトークンはASP.NETコアによって格納され、それぞれHttpContext.GetTokenAsync("access_token");HttpContext.GetTokenAsync("refresh_token");を使用して取得できます。

access_tokenを問題なく更新できます。この問題は、refresh_tokenが何らかの方法で期限切れ、取り消された、または無効になったときに発生します。

正しいフローは、ユーザーに再度ログインさせ、認証フロー全体を再度実行することです。次に、アプリケーションは返されたトークンの新しいセットを取得します。

私の質問は、これを最良かつ最も正しい方法で達成する方法です。 access_tokenの有効期限が切れた場合に更新しようとするカスタムミドルウェアを作成することにしました。その後、ミドルウェアは新しいトークンをHttpContextのAuthenticationPropertiesに設定し、後でパイプを介した呼び出しで使用できるようにします。

何らかの理由でトークンの更新に失敗した場合、ChallengeAsyncを再度呼び出す必要があります。ミドルウェアからChallengeAsyncを呼び出しています。

ここで、私はいくつかの興味深い動作に直面しています。ただし、ほとんどの場合、これは機能しますが、500のエラーが発生し、何が失敗しているかに関する有用な情報が得られないことがあります。ミドルウェアがChallengeAsyncをミドルウェアから呼び出そうとして問題を抱えているように見えます。おそらく、別のミドルウェアもコンテキストにアクセスしようとしています。

何が起こっているのかよくわかりません。これがこのロジックを配置するのに適切な場所であるかどうかはよくわかりません。多分他のどこかに、これをミドルウェアに入れてはいけません。おそらく、HttpClientのPollyが最適な場所です。

私はどんなアイデアに対してもオープンです。

あなたが提供できる助けをありがとう。

私のために働いたコードソリューション


MickaëlDerriey に感謝します(ヘルプと方向性について)

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;
    }
};
12
bugnuker

アクセストークンと更新トークンはASP.NETコアによって保存されます

トークンは、アプリケーションでユーザーを識別するCookieに保存されることに注意することが重要だと思います。

これが私の意見ですが、カスタムミドルウェアはトークンを更新するのに適した場所ではないと思います。これは、トークンを正常に更新した場合、既存のトークンを置き換えて、それを置き換える新しいCookieの形式でブラウザに送り返す必要があるためです。

これが、これを行うための最も適切な場所が、ASP.NET CoreによってCookieが読み取られるときだと思う理由です。すべての認証メカニズムはいくつかのイベントを公開します。 Cookieには、ValidatePrincipalと呼ばれるものがあります。これは、Cookieが読み取られ、IDが正常にデシリアライズされた後に、すべての要求で呼び出されます。

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を発行するよう指示します。

トークンを更新できない場合、または更新トークンの有効期限が切れており、ユーザーが先に進まないようにしたい場合、その同じクラスにはCookieに指示する RejectPrincipal メソッドがありますリクエストが匿名であるかのように処理するミドルウェア。

これの良い点は、MVCアプリが認証されたユーザーのみにアクセスを許可する場合、MVCはHTTP 401応答の発行を処理し、認証システムがそれをキャッチしてチャレンジに変換し、ユーザーがリダイレクトされることですアイデンティティプロバイダーへ。

GitHubの mderriey/TokenRenewal リポジトリでこれがどのように機能するかを示すコードがあります。意図は異なりますが、これらのイベントの使用方法の仕組みを示しています。

10