この問題を解決したコードについては以下を参照してください
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;
}
};
アクセストークンと更新トークンは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
リポジトリでこれがどのように機能するかを示すコードがあります。意図は異なりますが、これらのイベントの使用方法の仕組みを示しています。