English
Français

Blog of Denis VOITURON

for a better .NET world

JSON Web Token, sécuriser une WebAPI grâce au jeton JWT

Posted on 2017-12-19

JSON Web Token (JWT) est un standard ouvert (RFC 7519) pour échanger de l’information de manière sécurisée via un jeton signé. Par exemple un serveur pourrait émettre un jeton possédant l’affirmation “utilisateur identifié en tant qu’administrateur” et le fournir au client. Le client pourrait alors vérifier le jeton pour prouver que l’utilisateur est identifié en tant qu’administrateur (Wikipedia).

Personnellement, c’est une solution que je trouve élégante car

Flux général

Le principe est d’appeler une méthode qui génère un jeton (token) JWT contenant notamment, une date d’expiration et quelques méta-données, et signé pour éviter les altérations. Ce jeton est ensuite envoyé à toutes les requêtes, via l’en-tête HTTP.

Lorsque vous avez généré un jeton JWT, vous pouvez facilement le valider depuis le site web http://jwt.io.

Contenu du jeton JWT

Un jeton JWT est composé de trois parties : un en-tête, une charge utile et une signature :

Configuring WebAPIs

Configuration des WebAPI

La première étape consiste à créer un projet ASP.NET Core, via Visual Studio 2017 ou via Visual Studio Code. Ce projet pourra héberger vos WebAPI.

  1. Démarrez Visual Studio 2017 et créez un projet depuis les menus File / New / Project / Visual C# / .Net Core / ASP.NET Core Web Application.
  2. Ajoutez les librairies NuGet IdentityModel.Tokens.Jwt et System.Runtime.Serialization.Json, via le menu Project / Manage NuGet Packages.
  3. Adaptez la classe cs pour configurer le service JWT : ajoutez et configurez l’authentification JWT comme ci-dessous.
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();

        // Configure the JWT Authentication Service
        services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = "JwtBearer";
            options.DefaultChallengeScheme = "JwtBearer";
        })
        .AddJwtBearer("JwtBearer", jwtOptions =>
        {
            jwtOptions.TokenValidationParameters = new TokenValidationParameters()
            {
                // The SigningKey is defined in the TokenController class
                IssuerSigningKey = TokenController.SIGNING_KEY,
                ValidateIssuer = false,
                ValidateAudience = false,
                ValidateIssuerSigningKey = true,
                ValidateLifetime = true,
                ClockSkew = TimeSpan.FromMinutes(5)
            };
        });
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseAuthentication();  // Must be before app.UseMvc
        app.UseMvc();
    }
}
  1. Créez la classe cs et ajoutez la méthode de génération du Token. L’algorithme utilisé est basé sur SHA256. Il demande une clé secrète d’au moins 128 bits (16 caractères). Le site https://randomkeygen.com vous en donnera une rapidement (ou via le site VPNMentor.com pour les francophones… merci Madeline pour le lien).
public class TokenController : Controller
{
    private const string SECRET_KEY = "TQvgjeABMPOwCycOqah5EQu5yyVjpmVG";
    public static readonly SymmetricSecurityKey SIGNING_KEY = new
                  SymmetricSecurityKey(Encoding.UTF8.GetBytes(SECRET_KEY));

    [HttpGet]
    [Route("api/Token/{username}/{password}")]
    public IActionResult Get(string username, string password)
    {
        if (username == password)
            return new ObjectResult(GenerateToken(username));
        else
            return BadRequest();
    }

    // Generate a Token with expiration date and Claim meta-data.
    // And sign the token with the SIGNING_KEY
    private string GenerateToken(string username)
    {
        var token = new JwtSecurityToken(
            claims:    new Claim[] { new Claim(ClaimTypes.Name, username) },
            notBefore: new DateTimeOffset(DateTime.Now).DateTime,
            expires:   new DateTimeOffset(DateTime.Now.AddMinutes(60)).DateTime,
            signingCredentials: new SigningCredentials(SIGNING_KEY,
                                                SecurityAlgorithms.HmacSha256)
            );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}

Le jeton ainsi généré contient le nom de l’utilisateur (vous pouvez facilement ajouter un Claim de type Role), la plage de temps pendant laquelle le Token est valide, et l’algorithme de signature utilisé.

  1. La dernière étape consiste à demander une authentification des méthodes à sécuriser. Pour cela, utilisez l’attribut [Authorize]. Lors de la génération du Token (étape précédente), si vous aviez défini un Claim(ClaimTypes.Role, “Admin”), vous auriez pu l’utiliser dans cet attribut [Authorize(Roles = “Admin”)].
[Route("api/[controller]")]
public class ValuesController : Controller
{
    // GET api/values
    [HttpGet]
    [Authorize]
    public IEnumerable<;string> Get()
    {
        return new string[] { "value1", "value2" };
    }
}

Vérification de l’autorisation des méthodes

En exécutant votre projet WebAPI, vous ne pouvez plus appeler l’action api/values (définie dans la classe ValuesController), car vous recevrez une erreur de type “401. Unauthorized”. Pour pouvoir exécuter cette action, vous devez préalablement obtenir un Token et l’utiliser dans l’en-tête de la requête.

Pour tester vos requêtes, vous pouvez utiliser Postman ou la commande CURL.

Exemple.

 curl -i http://localhost/api/values

  HTTP/1.1 401 Unauthorized
  WWW-Authenticate: Bearer
  X-Powered-By: ASP.NET
  Content-Length: 0
 curl -i http://localhost/api/token/denis/mypassword

  HTTP/1.1 200 OK
  X-Powered-By: ASP.NET

  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2Fw
  Lm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiZGVuaXMiLCJuYmYiO
  jE1MTM3MDM5OTIsImV4cCI6MTUxMzcwNzU5Mn0.LepDs7Rm6bvcANUDHI2QaKW0A8ooN5
  iuALl7k9400HU
 curl -i http://localhost/api/values -H "Authorization: Bearer eyJhbGciOi..."

  HTTP/1.1 200 OK
  Content-Type: application/json; charset=utf-8
  X-Powered-By: ASP.NET

  ["value1","value2"]

Conclusion

JSON Web Token (JWT) est un standard ouvert qui ne demande que quelques lignes de configuration et qui permet de sécuriser facilement les WebAPI développées en C#. Ce protocole étant normalisé, il est compatible avec d’autres plateformes et d’autres langages : Java, Python, JavaScript, Go, Ruby, …

Le jeton utilisé lors des échanges de données est signé et peut contenir vos propres méta-données. Il est également très facile d’intégrer les WebAPI dans des projets de tests unitaires.

Langues

EnglishEnglish
FrenchFrançais

Suivez-moi

Articles récents