Claude-skill-registry add-auth-endpoint

Add authentication endpoints (register, login, refresh, logout) to NovaTune

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/add-auth-endpoint" ~/.claude/skills/majiayu000-claude-skill-registry-add-auth-endpoint && rm -rf "$T"
manifest: skills/data/add-auth-endpoint/SKILL.md
source content

Add Authentication Endpoint Skill

Add authentication endpoints (register, login, refresh, logout) to NovaTune.

Project Context

  • Auth endpoints go in:
    src/NovaTuneApp/NovaTuneApp.ApiService/Endpoints/AuthEndpoints.cs
  • Auth services go in:
    src/NovaTuneApp/NovaTuneApp.ApiService/Services/
  • DTOs go in:
    src/NovaTuneApp/NovaTuneApp.ApiService/Models/Auth/

Steps

1. Create Request/Response DTOs

Location:

src/NovaTuneApp/NovaTuneApp.ApiService/Models/Auth/

// RegisterRequest.cs
public record RegisterRequest(
    string Email,
    string DisplayName,
    string Password);

// LoginRequest.cs
public record LoginRequest(
    string Email,
    string Password);

// RefreshRequest.cs
public record RefreshRequest(string RefreshToken);

// AuthResponse.cs
public record AuthResponse(
    string AccessToken,
    string RefreshToken,
    int ExpiresIn,
    string TokenType = "Bearer");

// UserResponse.cs
public record UserResponse(
    string UserId,
    string Email,
    string DisplayName);

2. Create Auth Service Interface

Location:

src/NovaTuneApp/NovaTuneApp.ApiService/Services/IAuthService.cs

public interface IAuthService
{
    Task<UserResponse> RegisterAsync(RegisterRequest request, CancellationToken ct);
    Task<AuthResponse> LoginAsync(LoginRequest request, CancellationToken ct);
    Task<AuthResponse> RefreshAsync(RefreshRequest request, CancellationToken ct);
    Task LogoutAsync(string userId, string refreshToken, CancellationToken ct);
}

3. Create Auth Endpoints

Location:

src/NovaTuneApp/NovaTuneApp.ApiService/Endpoints/AuthEndpoints.cs

public static class AuthEndpoints
{
    public static void MapAuthEndpoints(this IEndpointRouteBuilder app)
    {
        var group = app.MapGroup("/auth")
            .WithTags("Authentication")
            .WithOpenApi();

        group.MapPost("/register", Register)
            .WithName("Register")
            .Produces<UserResponse>(StatusCodes.Status201Created)
            .ProducesProblem(StatusCodes.Status400BadRequest)
            .ProducesProblem(StatusCodes.Status409Conflict);

        group.MapPost("/login", Login)
            .WithName("Login")
            .Produces<AuthResponse>(StatusCodes.Status200OK)
            .ProducesProblem(StatusCodes.Status401Unauthorized)
            .ProducesProblem(StatusCodes.Status403Forbidden);

        group.MapPost("/refresh", Refresh)
            .WithName("RefreshToken")
            .Produces<AuthResponse>(StatusCodes.Status200OK)
            .ProducesProblem(StatusCodes.Status401Unauthorized);

        group.MapPost("/logout", Logout)
            .WithName("Logout")
            .RequireAuthorization()
            .Produces(StatusCodes.Status204NoContent);
    }

    private static async Task<IResult> Register(
        RegisterRequest request,
        IAuthService authService,
        CancellationToken ct)
    {
        var user = await authService.RegisterAsync(request, ct);
        return TypedResults.Created($"/users/{user.UserId}", user);
    }

    private static async Task<IResult> Login(
        LoginRequest request,
        IAuthService authService,
        CancellationToken ct)
    {
        var response = await authService.LoginAsync(request, ct);
        return TypedResults.Ok(response);
    }

    private static async Task<IResult> Refresh(
        RefreshRequest request,
        IAuthService authService,
        CancellationToken ct)
    {
        var response = await authService.RefreshAsync(request, ct);
        return TypedResults.Ok(response);
    }

    private static async Task<IResult> Logout(
        ClaimsPrincipal user,
        IAuthService authService,
        CancellationToken ct)
    {
        var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
        await authService.LogoutAsync(userId!, ct);
        return TypedResults.NoContent();
    }
}

4. Register in Program.cs

// Register services
builder.Services.AddScoped<IAuthService, AuthService>();

// Map endpoints (after app.Build())
app.MapAuthEndpoints();

Error Handling

Return RFC 7807 Problem Details for auth errors:

public static IResult InvalidCredentials() =>
    TypedResults.Problem(
        title: "Invalid Credentials",
        detail: "The email or password provided is incorrect.",
        statusCode: StatusCodes.Status401Unauthorized,
        type: "https://novatune.example/errors/invalid-credentials");

public static IResult AccountDisabled() =>
    TypedResults.Problem(
        title: "Account Disabled",
        detail: "This account has been disabled.",
        statusCode: StatusCodes.Status403Forbidden,
        type: "https://novatune.example/errors/account-disabled");

public static IResult EmailExists() =>
    TypedResults.Problem(
        title: "Email Already Registered",
        detail: "An account with this email already exists.",
        statusCode: StatusCodes.Status409Conflict,
        type: "https://novatune.example/errors/email-exists");

Rate Limiting

Apply rate limiting to auth endpoints:

group.MapPost("/login", Login)
    .RequireRateLimiting("auth-login");  // 10/min per IP, 5/min per account

group.MapPost("/register", Register)
    .RequireRateLimiting("auth-register");  // 10/min per IP

Testing

  • Unit tests:
    src/unit_tests/AuthServiceTests.cs
  • Integration tests:
    src/integration_tests/.../AuthEndpointsTests.cs

Key test scenarios:

  • Successful registration creates user with Active status
  • Login returns JWT with correct claims
  • Refresh rotates tokens and invalidates old one
  • Logout revokes only current session
  • Disabled user cannot login (403)
  • Rate limit returns 429 with Retry-After header