Files
mic-check/docs/api/04_authentication_and_authorization.md
James Wampler 6887d09f9c version-0.1 (#1)
Squash and merge of version-0.1

Co-authored-by: James Wampler <james@wamp.dev>
Co-committed-by: James Wampler <james@wamp.dev>
2026-07-01 13:30:07 -07:00

6.4 KiB
Executable File

Step 4: Authentication & Authorization

Goal

Implement the two authentication schemes Flagsmith uses — Environment Key (public, for Flags API) and API Key (secret, for Admin API) — plus JWT for dashboard users, and role-based authorization.


Authentication Schemes

1. Environment Key (Flags API)

  • Delivered via X-Environment-Key header
  • Public — safe for client-side exposure
  • Identifies which environment to serve flags for
  • No user identity implied

Implementation:

// MicCheck.Api/Authentication/EnvironmentKeyAuthenticationHandler.cs
public class EnvironmentKeyAuthenticationHandler 
    : AuthenticationHandler<AuthenticationSchemeOptions>
{
    // Reads X-Environment-Key header
    // Looks up Environment from DB by ApiKey value
    // Sets ClaimsPrincipal with EnvironmentId claim
    // Scheme name: "EnvironmentKey"
}

2. Organisation API Key (Admin API)

  • Delivered via Authorization: Api-Key <TOKEN> header
  • Secret — never expose client-side
  • Identifies the organization and grants admin access
  • Stored hashed in DB; prefix shown for identification

Implementation:

// MicCheck.Api/Authentication/ApiKeyAuthenticationHandler.cs
public class ApiKeyAuthenticationHandler
    : AuthenticationHandler<AuthenticationSchemeOptions>
{
    // Parses "Api-Key <TOKEN>" from Authorization header
    // Hashes the provided token
    // Looks up ApiKey record by hash
    // Validates IsActive and ExpiresAt
    // Sets ClaimsPrincipal with OrganizationId and UserId claims
    // Scheme name: "ApiKey"
}

API key hashing:

  • Use SHA256 for hashing stored keys
  • Store prefix (first 8 chars) for display/identification only
  • Generate new keys with RandomNumberGenerator.GetBytes(32) → Base64URL encoded

3. JWT (Dashboard Users)

  • Standard bearer token authentication
  • Used for the admin web UI
  • Short-lived access token + refresh token pattern

Implementation:

builder.Services.AddAuthentication()
    .AddJwtBearer("Bearer", options => {
        options.TokenValidationParameters = new() {
            ValidIssuer = config["Jwt:Issuer"],
            ValidAudience = config["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(config["Jwt:Secret"]!))
        };
    });

Authorization Policies

Define named policies in MicCheck.Api/Authorization/:

// Policies
public static class AuthorizationPolicies
{
    public const string FlagsApiAccess = "FlagsApiAccess";
    public const string AdminApiAccess = "AdminApiAccess";
    public const string OrganizationAdmin = "OrganizationAdmin";
}

Policy registrations:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy(AuthorizationPolicies.FlagsApiAccess, policy =>
        policy.AddAuthenticationSchemes("EnvironmentKey")
              .RequireClaim("EnvironmentId"));

    options.AddPolicy(AuthorizationPolicies.AdminApiAccess, policy =>
        policy.AddAuthenticationSchemes("ApiKey", "Bearer")
              .RequireAuthenticatedUser());

    options.AddPolicy(AuthorizationPolicies.OrganizationAdmin, policy =>
        policy.AddAuthenticationSchemes("ApiKey", "Bearer")
              .RequireClaim("OrganizationRole", "Admin"));
});

Role-Based Access Control (RBAC)

Organization Roles

  • Admin — full access to all projects and settings
  • User — access to assigned projects only

Project-Level Permissions

// MicCheck.Api/Authorization/ProjectPermission.cs
public enum ProjectPermission
{
    ViewProject,
    CreateFeature, EditFeature, DeleteFeature,
    CreateEnvironment, EditEnvironment, DeleteEnvironment,
    CreateSegment, EditSegment, DeleteSegment,
    ManageWebhooks,
    ViewAuditLog
}

// MicCheck.Api/Authorization/UserProjectPermission.cs
public class UserProjectPermission
{
    public int UserId { get; init; }
    public int ProjectId { get; init; }
    public ICollection<ProjectPermission> Permissions { get; init; } = [];
    public bool IsAdmin { get; set; } // grants all permissions
}

Authorization requirement handler:

// MicCheck.Api/Authorization/ProjectPermissionRequirementHandler.cs
// Checks UserProjectPermission for the current user and requested project
// Organization admins bypass all project-level checks

User Model

// MicCheck.Api/Users/User.cs
public class User
{
    public int Id { get; init; }
    public required string Email { get; set; }
    public required string PasswordHash { get; set; }
    public required string FirstName { get; set; }
    public required string LastName { get; set; }
    public bool IsActive { get; set; }
    public bool IsTwoFactorEnabled { get; set; }
    public DateTimeOffset CreatedAt { get; init; }
    public DateTimeOffset? LastLoginAt { get; set; }
    public ICollection<OrganizationUser> Organizations { get; init; } = [];
}

Password hashing: Use ASP.NET Core's IPasswordHasher<User>.


Auth Endpoints

POST /api/v1/auth/login          → Returns JWT access + refresh tokens
POST /api/v1/auth/refresh        → Exchanges refresh token for new access token
POST /api/v1/auth/logout         → Revokes refresh token
POST /api/v1/auth/register       → Creates new user + organization

POST /api/v1/organisations/{id}/api-keys/   → Create new API key
GET  /api/v1/organisations/{id}/api-keys/   → List API keys (prefix only)
DELETE /api/v1/organisations/{id}/api-keys/{keyId}/ → Revoke key

Rate Limiting

Apply to Admin API:

  • 500 requests per minute per API key
  • Use ASP.NET Core's built-in rate limiting middleware (Microsoft.AspNetCore.RateLimiting)
builder.Services.AddRateLimiter(options =>
    options.AddFixedWindowLimiter("AdminApi", limiter => {
        limiter.PermitLimit = 500;
        limiter.Window = TimeSpan.FromMinutes(1);
        limiter.QueueLimit = 0;
    }));

Acceptance Criteria

  • X-Environment-Key header resolves to correct environment; invalid key returns 401
  • Authorization: Api-Key <TOKEN> validates against hashed DB record; invalid key returns 401
  • JWT tokens are issued and validated correctly
  • Organization admins have access to all project operations
  • Project-level permission checks block unauthorized users with 403
  • Rate limit returns 429 after 500 Admin API requests within a minute
  • Unit tests cover: key hashing, auth handler success/failure paths, permission checks