ワンクリックで
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).