一键导入
auth-jwt
Use when setting up JWT authentication, token generation, or refresh token flows.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when setting up JWT authentication, token generation, or refresh token flows.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when designing gRPC services, proto files, or adding gRPC-Web or JSON transcoding.
Use when writing async code, propagating CancellationTokens, or fixing async/await pitfalls.
Use when applying or enforcing C# coding style — namespaces, sealed classes, var usage, XML docs.
Use when applying modern C# idioms — records, pattern matching, primary constructors, collection expressions.
Use when registering services, choosing lifetimes, or implementing DI patterns like decorator or keyed services.
Use when selecting or implementing design patterns in C# — factory, builder, strategy, decorator, or mediator.
| name | auth-jwt |
| description | Use when setting up JWT authentication, token generation, or refresh token flows. |
| metadata | {"category":"security","agent":"api-designer"} |
| when_to_use | When configuring JWT authentication, token generation, or refresh token flows |
JsonWebTokenHandler (.NET 10+) or JwtSecurityTokenHandler for token generation// Program.cs
builder.Services.AddAuthentication(
JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters =
new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(
builder.Configuration["Jwt:Key"]!)),
ClockSkew = TimeSpan.FromSeconds(30) // default is 5 min
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
public sealed class JwtOptions
{
public const string SectionName = "Jwt";
[Required]
public required string Issuer { get; init; }
[Required]
public required string Audience { get; init; }
[Required, MinLength(32)]
public required string Key { get; init; }
[Range(1, 1440)]
public int AccessTokenExpiryMinutes { get; init; } = 60;
[Range(1, 43200)]
public int RefreshTokenExpiryMinutes { get; init; } = 10080; // 7 days
}
// Registration
builder.Services.AddOptions<JwtOptions>()
.BindConfiguration(JwtOptions.SectionName)
.ValidateDataAnnotations()
.ValidateOnStart();
public interface ITokenService
{
TokenResponse GenerateTokens(
User user, IEnumerable<string> permissions);
}
public sealed class TokenService(IOptions<JwtOptions> options)
: ITokenService
{
public TokenResponse GenerateTokens(
User user, IEnumerable<string> permissions)
{
var claims = new List<Claim>
{
new(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
new(JwtRegisteredClaimNames.Email, user.Email),
new(JwtRegisteredClaimNames.Jti,
Guid.NewGuid().ToString()),
new("permissions", string.Join(",", permissions))
};
foreach (var role in user.Roles)
claims.Add(new Claim(ClaimTypes.Role, role));
var key = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(options.Value.Key));
var credentials = new SigningCredentials(
key, SecurityAlgorithms.HmacSha256);
var expiry = DateTime.UtcNow.AddMinutes(
options.Value.AccessTokenExpiryMinutes);
var token = new JwtSecurityToken(
issuer: options.Value.Issuer,
audience: options.Value.Audience,
claims: claims,
expires: expiry,
signingCredentials: credentials);
return new TokenResponse(
AccessToken: new JwtSecurityTokenHandler().WriteToken(token),
ExpiresAt: expiry,
RefreshToken: GenerateRefreshToken());
}
private static string GenerateRefreshToken()
=> Convert.ToBase64String(RandomNumberGenerator.GetBytes(64));
}
public sealed record TokenResponse(
string AccessToken,
DateTime ExpiresAt,
string RefreshToken);
public sealed record RefreshTokenCommand(
string RefreshToken) : IRequest<Result<TokenResponse>>;
internal sealed class RefreshTokenHandler(
ITokenService tokenService,
IRefreshTokenRepository refreshTokenRepo,
IUserRepository userRepo)
: IRequestHandler<RefreshTokenCommand, Result<TokenResponse>>
{
public async Task<Result<TokenResponse>> Handle(
RefreshTokenCommand request, CancellationToken ct)
{
var stored = await refreshTokenRepo
.FindByTokenAsync(request.RefreshToken, ct);
if (stored is null || stored.IsExpired || stored.IsRevoked)
return Result<TokenResponse>.Failure(
Error.Unauthorized("Token.Invalid",
"Invalid or expired refresh token"));
// Rotate: revoke old token
stored.Revoke();
var user = await userRepo.FindAsync(stored.UserId, ct);
var permissions = await userRepo
.GetPermissionsAsync(user!.Id, ct);
var newToken = tokenService.GenerateTokens(
user!, permissions);
// Store new refresh token
await refreshTokenRepo.StoreAsync(
new RefreshToken(user!.Id, newToken.RefreshToken,
DateTime.UtcNow.AddDays(7)),
ct);
await refreshTokenRepo.SaveChangesAsync(ct);
return Result<TokenResponse>.Success(newToken);
}
}
app.MapPost("/auth/login", async (
LoginRequest request,
ISender sender, CancellationToken ct) =>
{
var result = await sender.Send(
new LoginCommand(request.Email, request.Password), ct);
return result.Match(
token => Results.Ok(token),
error => Results.Unauthorized());
});
app.MapPost("/auth/refresh", async (
RefreshTokenRequest request,
ISender sender, CancellationToken ct) =>
{
var result = await sender.Send(
new RefreshTokenCommand(request.RefreshToken), ct);
return result.Match(
token => Results.Ok(token),
error => Results.Unauthorized());
});
public interface ICurrentUserService
{
Guid UserId { get; }
string Email { get; }
IReadOnlyList<string> Roles { get; }
IReadOnlyList<string> Permissions { get; }
}
internal sealed class CurrentUserService(
IHttpContextAccessor httpContextAccessor)
: ICurrentUserService
{
private ClaimsPrincipal User =>
httpContextAccessor.HttpContext?.User
?? throw new UnauthorizedAccessException();
public Guid UserId => Guid.Parse(
User.FindFirstValue(JwtRegisteredClaimNames.Sub)!);
public string Email =>
User.FindFirstValue(JwtRegisteredClaimNames.Email)!;
public IReadOnlyList<string> Roles =>
User.FindAll(ClaimTypes.Role)
.Select(c => c.Value).ToList();
public IReadOnlyList<string> Permissions =>
(User.FindFirstValue("permissions") ?? "")
.Split(',', StringSplitOptions.RemoveEmptyEntries)
.ToList();
}
AddJwtBearer in Program.csJwtSecurityTokenHandler or JsonWebTokenHandler usageTokenValidationParameters configurationITokenService or similar abstraction[Authorize] attributes on controllers/endpointsValidateOnStart for JWT configurationICurrentUserService for claim access in handlersThis skill depends on the always-on security convention rule:
The rule is loaded into context whenever this skill is active (FR-011 universal whitelist).