JWT+ASP.NET Core integration solution

created at 02-10-2022 views: 9

JWT

After JSON Web Token is digitally signed, it cannot be forged, an open standard that can securely transmit JSON objects between parties (RFC 7519)

Create projects and solutions

dotnet new webapi -n SampleApi
cd SampleApi
dotnet new sln -n SampleApp
dotnet sln add .\SampleApi.csproj

reference package

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

This package already depends on Microsoft.IdentityModel.Tokens, System.IdentityModel.Tokens.Jwt, this package is provided by the Azure AD team, so it is not in the aspnetcore6 runtime.

Or directly modify jwtaspnetcore.csproj, reference package

<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" />

appsettings.json

  "Authentication": {
    "JwtBearer": {
      "Issuer": "http://api.sampleapi.com",
      "Audience": "SampleApi",
      "SecurityKey": "SecurityKey23456"
    }
  }
  • Issuer: The issuer of the token. Generally, it is written as a domain name, but it can be arbitrarily
  • Audience : to whom. Generally written as the project name, actually can be arbitrary
  • SecurityKey: KEY for signature verification; at least 128bit, that is, more than 16 English characters, actually any English characters

Define a JwtSettings


public class JwtSettings
{
    public JwtSettings(byte[] key, string issuer, string audience)
    {
        Key = key;
        Issuer = issuer;
        Audience = audience;
    }

    /// <summary>
    /// issuer of the token
    /// </summary>
    public string Issuer { get; }

    /// <summary>
    /// to whom
    /// </summary>
    public string Audience { get; }

    public byte[] Key { get; }

    public TokenValidationParameters TokenValidationParameters => new TokenValidationParameters
    {
        //验证Issuer和Audience
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateIssuerSigningKey = true,
        //是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
        ValidateLifetime = true,
        ValidIssuer = Issuer,
        ValidAudience = Audience,
        IssuerSigningKey = new SymmetricSecurityKey(Key)
    };

    public static JwtSettings FromConfiguration(IConfiguration configuration)
    {
        var issuser = configuration["Authentication:JwtBearer:Issuer"] ?? "default_issuer";
        var auidence = configuration["Authentication:JwtBearer:Audience"] ?? "default_auidence";
        var securityKey = configuration["Authentication:JwtBearer:SecurityKey"] ?? "default_securitykey";

        byte[] key = Encoding.ASCII.GetBytes(securityKey);

        return new JwtSettings(key, issuser, auidence);
    }
}

Middleware Reference

app.UseAuthentication();//Authentication
app.UseAuthorization();//Authorization

Defining JWT extension method service injection

    public static IServiceCollection AddJwt(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddScoped<IStorageUserService, StorageUserService>();

        var jwtSettings = JwtSettings.FromConfiguration(configuration);
        services.AddSingleton(jwtSettings);

        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options => options.TokenValidationParameters = jwtSettings.TokenValidationParameters);

        return services;
    }

Citation service

services.AddJwt(Configuration);

Define a database entity class, database access is simulated data

public class SysUser
{
     public int Id { get; set; }
     public string UserName { get; set; }
}
public interface IStorageUserService
{
     /// <summary>
     /// Authenticate the user based on the login
     /// </summary>
     /// <param name="loginInfo"></param>
     /// <returns></returns>
     Task<SysUser> CheckPasswordAsync(LoginInfo loginInfo);
}
public class StorageUserService : IStorageUserService
{
     public async Task<SysUser> CheckPasswordAsync(LoginInfo loginInfo)
     {
         return await Task.FromResult(
           new SysUser
           {
             Id = new Random().Next(10000),
             UserName = loginInfo.UserName
           }
         );
     }
}

AuthController Login GenerateToken

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using SampleApi.Models;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

namespace SampleApi.Auth;

/// <summary>
/// Login authentication personal information
/// </summary>
[ApiController]
[Route("/api/[controller]/[action]")]
[AllowAnonymous]
public class AuthController : ControllerBase
{
    private readonly IStorageUserService _userService;
    private readonly JwtSettings _jwtSettings;

    public AuthController(JwtSettings jwtSettings, IStorageUserService userService)
    {
        _jwtSettings = jwtSettings;
        _userService = userService;
    }

    /// <summary>
    /// Login, generate access to Toekn
    /// </summary>
    /// <param name="loginInfo"></param>
    /// <returns></returns>
    [HttpPost]
    public async Task<IActionResult> GenerateToken(LoginInfo loginInfo)
    {
        SysUser user = await _userService.CheckPasswordAsync(loginInfo);
        if (user == null)
        {
            return Ok(new
            {
                Status = false,
                Message = "Incorrect account or password"
            });
        }

        var claims = new List<Claim>();

        claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
        claims.Add(new Claim(ClaimTypes.Name, user.UserName));

        var key = new SymmetricSecurityKey(_jwtSettings.Key);
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
        var token = new JwtSecurityToken(
            issuer: _jwtSettings.Issuer,
            audience: _jwtSettings.Audience,
            claims: claims,
            expires: DateTime.Now.AddMinutes(120),
            signingCredentials: creds
            );
        return Ok(new
        {
            Status = true,
            Token = new JwtSecurityTokenHandler().WriteToken(token)
        });
    }
}

aspnetcore6 integrates swagger by default, running the project directly, in fact, simulates the database request, so click the login interface.

{
  "status": true,
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6Ijc4NjciLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoic3RyaW5nIiwiZXhwIjoxNjQzMDMyNzA1LCJpc3MiOiJodHRwOi8vYXBpLnNhbXBsZWFwaS5jb20iLCJhdWQiOiJTYW1wbGVBcGkifQ.Rl8XAt2u0aZRxEJw2mVUnV6S9WzQ65qUYjqXDTneCxE"
}

When testing with Swagger, add a configurable global request header. Add an extension method.

services.AddSwagger(Configuration);
  public static IServiceCollection AddSwagger(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddSwaggerGen(options =>
        {
            try
            {
                options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{typeof(Startup).Assembly.GetName().Name}.xml"), true);
            }
            catch (Exception ex)
            {
                Log.Warning(ex.Message);
            }
            options.SwaggerDoc("v1", new OpenApiInfo
            {
                Title = "SampleApp - HTTP API",
                Version = "v1",
                Description = "The SampleApp Microservice HTTP API. This is a Data-Driven/CRUD microservice sample"
            });

            options.AddSecurityRequirement(new OpenApiSecurityRequirement()
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference()
                            {
                                Id =  "Bearer",
                                Type = ReferenceType.SecurityScheme
                            }
                        },
                        Array.Empty<string>()
                    }
                });
            options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, new OpenApiSecurityScheme
            {
                Description = "JWT authorization (data will be transmitted in the request header) Parameter structure: \"Authorization: Bearer {token}\"",
                Name = "Authorization", //jwt default parameter name
                In = ParameterLocation.Header, //The location where jwt stores the Authorization information by default (in the request header)
                Type = SecuritySchemeType.ApiKey
            });

        });
        services.AddEndpointsApiExplorer();

        return services;

    }

Get current user information

/// <summary>
     /// Encode Token
     /// </summary>
     /// <param name="token"></param>
     /// <returns></returns>
     [HttpGet]
     [AllowAnonymous]
     public CurrentUser DecodeToken(string token)
     {
         var jwtTokenHandler = new JwtSecurityTokenHandler();

         if (jwtTokenHandler.CanReadToken(token))
         {
             JwtPayload jwtPayload = new JwtSecurityTokenHandler().ReadJwtToken(token).Payload;
             string? userIdOrNull = jwtPayload.Claims.FirstOrDefault(r => r.Type == ClaimTypes.NameIdentifier)?.Value;
             string? UserName = jwtPayload.Claims.FirstOrDefault(r => r.Type == ClaimTypes.Name)?.Value;
             CurrentUser currentUser = new CurrentUser
             {
                 UserId = userIdOrNull == null ? null : Convert.ToInt32(userIdOrNull),
                 UserName = UserName
             };
             return currentUser;
         }
         return null;
     }

Get user information from request headers

IStorageUserService adds an interface, the implementation of StorageUserService, creates a CurrentUser class

public class StorageUserService : IStorageUserService
{
    private readonly IHttpContextAccessor _contextAccessor;

    public StorageUserService(IHttpContextAccessor contextAccessor)
    {
        _contextAccessor = contextAccessor;
    }

    public async Task<CurrentUser> GetUserByRequestContext()
    {
        var user = _contextAccessor.HttpContext.User;

        string? userIdOrNull = user.Claims?.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
        string? UserName = user.Claims?.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;

        CurrentUser currentUser = new CurrentUser
        {
            IsAuthenticated = user.Identity.IsAuthenticated,
            UserId = userIdOrNull == null ? null : Convert.ToInt32(userIdOrNull),
            UserName = UserName
        };
        return await Task.FromResult(currentUser);
    }
}

public class CurrentUser
{
    /// <summary>
    /// Whether to log in
    /// </summary>
    public bool IsAuthenticated { get; set; }
    /// <summary>
    /// UserId
    /// </summary>
    public int? UserId { get; set; }
    /// <summary>
    /// username
    /// </summary>
    public string? UserName { get; set; }
}
public interface IStorageUserService
{
    /// <summary>
    /// Carry Authorization:Bearer+space+AccessToken to obtain the current login information according to the Request Header
    /// </summary>
    /// <returns></returns>
    Task<CurrentUser> GetUserByRequestContext();
}

AuthController calls the service

/// <summary>
     /// Carry Authorization:Bearer+space+AccessToken to obtain the current login information according to the Request Header
     /// </summary>
     /// <returns></returns>
     [HttpGet]
     [Authorize]
     public async Task<CurrentUser> GetUserByRequestContext()
     {
         return await _userService.GetUserByRequestContext();
     }

In the upper right corner of swagger, click Authorize, the parameter structure of the header: "Authorization: Bearer+space+{token}"

.NET + JWT

JSON Web Token Libraries - jwt.io As you can see, .NET has 6 class libraries that implement JWT.

There are two commonly used ones.

created at:02-10-2022
edited at: 02-10-2022: