books/backend/Books.Api/Authentication/UserExtensions.cs
Nicolaj Hartmann 926085eeab Add OpenID Connect + API Key authentication
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>
2026-01-18 11:49:29 +01:00

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);