web-dev-qa-db-ja.com

asp.netコアWeb API(サードパーティなし)でJWTリフレッシュトークンを実装する方法は?

JWTを使用しているasp.netコアを使用してWeb APIを実装しています。学習しようとしているので、IdentityServer4などのサードパーティのソリューションを使用していません。

JWT構成が機能するようになりましたが、JWTの有効期限が切れたときに更新トークンを実装する方法に困惑しています。

以下は、startup.cs内の私のConfigureメソッドのサンプルコードです。

app.UseJwtBearerAuthentication(new JwtBearerOptions()
{
    AuthenticationScheme = "Jwt",
    AutomaticAuthenticate = true,
    AutomaticChallenge = true,
    TokenValidationParameters = new TokenValidationParameters()
    {
        ValidAudience = Configuration["Tokens:Audience"],
        ValidIssuer = Configuration["Tokens:Issuer"],
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"])),
        ValidateLifetime = true,
        ClockSkew = TimeSpan.Zero
    }
});

以下は、JWTの生成に使用されるControllerメソッドです。テスト用に有効期限を30秒に設定しました。

    [Route("Token")]
    [HttpPost]
    public async Task<IActionResult> CreateToken([FromBody] CredentialViewModel model)
    {
        try
        {
            var user = await _userManager.FindByNameAsync(model.Username);

            if (user != null)
            {
                if (_hasher.VerifyHashedPassword(user, user.PasswordHash, model.Password) == PasswordVerificationResult.Success)
                {
                    var userClaims = await _userManager.GetClaimsAsync(user);

                    var claims = new[]
                    {
                        new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
                        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
                    }.Union(userClaims);

                    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwt.Key));
                    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

                    var token = new JwtSecurityToken(
                            issuer: _jwt.Issuer,
                            audience: _jwt.Audience,
                            claims: claims,
                            expires: DateTime.UtcNow.AddSeconds(30),
                            signingCredentials: creds
                        );

                    return Ok(new
                    {
                        access_token = new JwtSecurityTokenHandler().WriteToken(token),
                        expiration = token.ValidTo
                    });
                }
            }
        }
        catch (Exception)
        {

        }

        return BadRequest("Failed to generate token.");
    }

いくつかのガイダンスに非常に感謝します。

18
DJDJ

まず、リフレッシュトークンを生成し、どこかに保存する必要があります。これは、必要に応じて無効にできるようにするためです。すべてのデータがトークン内に含まれるアクセストークンと同じパターンに従う場合、間違った手に渡るトークンを使用して、更新トークンの有効期間中に新しいアクセストークンを生成できます。本当に長い時間になることがあります。

では、何を固執する必要がありますか?

GUIDで問題ありません。新しいアクセストークン、ほとんどの場合はユーザー名を発行できるデータも必要です。ユーザー名は、VerifyHashedPassword(...)部分をスキップできますが、残りの部分については同じロジックに従います。

更新トークンを取得するには、通常、スコープ「offline_access」を使用します。これは、トークン要求を行うときにモデル(CredentialViewModel)で提供するものです。通常のアクセストークンリクエストとは異なり、ユーザー名とパスワードを提供する必要はなく、代わりに更新トークンを提供します。更新トークンでリクエストを取得する場合、永続化された識別子を検索し、見つかったユーザーのトークンを発行します。

以下は、ハッピーパスの擬似コードです。

[Route("Token")]
[HttpPost]
public async Task<IActionResult> CreateToken([FromBody] CredentialViewModel model)
{
    if (model.GrantType is "refresh_token")
    {
        // Lookup which user is tied to model.RefreshToken
        // Generate access token from the username (no password check required)
        // Return the token (access + expiration)
    }
    else if (model.GrantType is "password")
    {
        if (model.Scopes contains "offline_access")
        {
            // Generate access token
            // Generate refresh token (random GUID + model.username)
            // Persist refresh token
            // Return the complete token (access + refresh + expiration)
        }
        else
        {
            // Generate access token
            // Return the token (access + expiration)
        }
    }
}
17
naslund