web-dev-qa-db-fra.com

Comment implémenter les jetons de rafraîchissement JWT dans l'API Web principale asp.net (pas de tierce partie)?

Je suis en train de mettre en œuvre une API Web en utilisant le noyau asp.net qui utilise JWT. Je n'utilise pas de solution tierce telle que IdentityServer4 que j'essaie d'apprendre.

J'ai réussi à faire fonctionner la configuration JWT, mais je suis perplexe sur la façon d'implémenter des jetons d'actualisation à l'expiration du JWT.

Voici un exemple de code dans ma méthode Configure dans startup.cs.

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
    }
});

Vous trouverez ci-dessous la méthode du contrôleur utilisée pour générer le JWT. J'ai défini l'expiration à 30 secondes à des fins de test.

    [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.");
    }

Serait très reconnaissant de quelques conseils.

18
DJDJ

Tout d'abord, vous devez générer un jeton d'actualisation et le conserver quelque part. C'est parce que vous voulez pouvoir l'invalider si nécessaire. Si vous deviez suivre le même modèle qu'un jeton d'accès - où toutes les données sont contenues dans le jeton - un jeton qui se retrouve entre de mauvaises mains peut être utilisé pour générer de nouveaux jetons d'accès pour la durée de vie du jeton d'actualisation, qui peut être très long.

Alors, de quoi avez-vous besoin pour persister?

Vous avez besoin d'un identifiant unique d'une sorte qui n'est pas facile à deviner, un GUID fera l'affaire. Vous avez également besoin des données pour pouvoir émettre un nouveau jeton d'accès, probablement un nom d'utilisateur. Ayant un nom d'utilisateur, vous pouvez ensuite ignorer la partie VerifyHashedPassword (...) mais pour le reste, suivez simplement la même logique.

Pour obtenir un jeton d'actualisation, vous utilisez normalement la portée "offline_access", c'est quelque chose que vous fournissez dans votre modèle (CredentialViewModel) lorsque vous effectuez une demande de jeton. Contrairement à une demande de jeton d'accès normal, vous n'avez pas besoin de fournir votre nom d'utilisateur et votre mot de passe, mais plutôt le jeton d'actualisation. Lorsque vous obtenez une demande avec un jeton d'actualisation, vous recherchez l'identifiant persistant et émettez un jeton pour l'utilisateur trouvé.

Voici un pseudo code pour le chemin heureux:

[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