ASP.NET Core WebAPI With JWT

2024-07-01

筆記如何在 ASP.NET Core WebAPI 專案使用 JWT 驗證。

logo

說明

首先安裝 Nuget Package Microsoft.AspNetCore.Authentication.JwtBearer

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

接著設定 appsettings.json 加入 JWT 的 Key。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "Jwt": {
    "Key": "Your_Very_Strong_Secret_Key_That_Is_At_Least_32_Characters_Long"
  },
  "AllowedHosts": "*"
}

AuthController.cs

接著設定登入用途以及提供 JWT Token 的 Controller,這個 Controller 會驗證帳號與密碼,並回傳 JWT Token,基於示範,帳號與密碼是固定的,實際應用中應該要透過資料庫或其他方式驗證 😲

using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

[ApiController]
[Route("[controller]")]
public class AuthController : ControllerBase
{
    private readonly IConfiguration _configuration;

    public AuthController(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    [HttpPost("login")]
    public IActionResult Login([FromBody] LoginModel login)
    {
        // Validate the user credentials (this is just a basic example)
        if (login.Username != "test" || login.Password != "password")
            return Unauthorized();

        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]);
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new Claim[]
            {
                new Claim(ClaimTypes.Name, login.Username)
            }),
            Expires = DateTime.UtcNow.AddDays(7),
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
        };
        var token = tokenHandler.CreateToken(tokenDescriptor);
        var tokenString = tokenHandler.WriteToken(token);

        return Ok(new { Token = tokenString });
    }
}

public class LoginModel
{
    public string Username { get; set; }
    public string Password { get; set; }
}

Program.cs

將驗證服務加入到 WebAPI 中。

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container
var key = Encoding.ASCII.GetBytes(builder.Configuration["Jwt:Key"]);

builder.Services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.RequireHttpsMetadata = false;
        options.SaveToken = true;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(key),
            ValidateIssuer = false,
            ValidateAudience = false
        };
    });

builder.Services.AddAuthorization();
builder.Services.AddControllers();

var app = builder.Build();

// Configure the HTTP request pipeline
app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

Application

實際的應用,透過 [Authorize] 屬性來限制存取。

[ApiController]
[Route("[controller]")]
[Authorize]
public class WeatherForecastController : ControllerBase
{
    // GET /weatherforecast
    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        var rng = new Random();
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55)
        })
        .ToArray();
    }
}

Test with .http file

POST https://localhost:7130/Auth/Login
Content-Type: application/json

{
    "Username": "test",
    "Password": "password"
}
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InRlc3QiLCJuYmYiOjE3MTk3OTM4ODYsImV4cCI6MTcyMDM5ODY4NiwiaWF0IjoxNzE5NzkzODg2fQ.GuuqemttKA8TNOo69ial4ztQNaK_PhEdCveI7qNFTN0"
}

取得的 Token 可以透過 Dev Tools 來 Decode。

接著實際進行資源存取,如果沒有輸入 Token 則會回傳 401,如果有輸入正確的 Token 則會回傳資源。

GET https://localhost:7130/WeatherForecast

###
GET https://localhost:7130/WeatherForecast
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InRlc3QiLCJuYmYiOjE3MTk3OTI2NTAsImV4cCI6MTcyMDM5NzQ1MCwiaWF0IjoxNzE5NzkyNjUwfQ.Tu2N2yPcopFJLWvOcXvM8IRkRaDQcerXxe4fwQz-yuw