Backend: - Cookie + OIDC + API Key authentication schemes - ApiKeyAuthenticationHandler with SHA-256 validation and 24h cache - AuthController with login/logout/profile endpoints - API Key domain model (EventFlow aggregate, events, commands) - ApiKeyReadModel and repository for key validation - Database migration 002_ApiKeys.sql - CORS configuration for frontend Frontend: - authService.ts for login/logout/profile API calls - authStore.ts (Zustand) for user context state - ProtectedRoute component for route guards - Header updated with user display and logout - GraphQL client with credentials: include 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
34 lines
1.1 KiB
C#
34 lines
1.1 KiB
C#
using System.Security.Claims;
|
|
|
|
namespace Books.Api.Authentication;
|
|
|
|
public static class UserExtensions
|
|
{
|
|
public static UserContext? GetUserContext(this ClaimsPrincipal? principal)
|
|
{
|
|
if (principal?.Identity?.IsAuthenticated != true)
|
|
return null;
|
|
|
|
return new UserContext(
|
|
Id: principal.FindFirst(ClaimTypes.NameIdentifier)?.Value,
|
|
Email: principal.FindFirst(ClaimTypes.Email)?.Value
|
|
?? principal.FindFirst("preferred_username")?.Value,
|
|
Name: principal.FindFirst(ClaimTypes.GivenName)?.Value
|
|
?? principal.FindFirst(ClaimTypes.Name)?.Value,
|
|
CompanyId: principal.FindFirst("company_id")?.Value,
|
|
IsApiKey: principal.FindFirst(ClaimTypes.AuthenticationMethod)?.Value == "api_key"
|
|
);
|
|
}
|
|
|
|
public static string? GetClaimValue(this ClaimsPrincipal principal, string claimType)
|
|
{
|
|
return principal.FindFirst(claimType)?.Value;
|
|
}
|
|
}
|
|
|
|
public record UserContext(
|
|
string? Id,
|
|
string? Email,
|
|
string? Name,
|
|
string? CompanyId,
|
|
bool IsApiKey);
|