Building out navigation in the admin site
This commit is contained in:
214
docs/api/04_authentication_and_authorization.md
Normal file
214
docs/api/04_authentication_and_authorization.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# 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:**
|
||||
```csharp
|
||||
// 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:**
|
||||
```csharp
|
||||
// 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:**
|
||||
```csharp
|
||||
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/`:
|
||||
|
||||
```csharp
|
||||
// Policies
|
||||
public static class AuthorizationPolicies
|
||||
{
|
||||
public const string FlagsApiAccess = "FlagsApiAccess";
|
||||
public const string AdminApiAccess = "AdminApiAccess";
|
||||
public const string OrganizationAdmin = "OrganizationAdmin";
|
||||
}
|
||||
```
|
||||
|
||||
**Policy registrations:**
|
||||
```csharp
|
||||
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
|
||||
|
||||
```csharp
|
||||
// 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:
|
||||
|
||||
```csharp
|
||||
// MicCheck.Api/Authorization/ProjectPermissionRequirementHandler.cs
|
||||
// Checks UserProjectPermission for the current user and requested project
|
||||
// Organization admins bypass all project-level checks
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## User Model
|
||||
|
||||
```csharp
|
||||
// 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`)
|
||||
|
||||
```csharp
|
||||
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
|
||||
Reference in New Issue
Block a user