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>
41 lines
1.1 KiB
C#
41 lines
1.1 KiB
C#
using Books.Api.Authentication;
|
|
using Microsoft.AspNetCore.Authentication;
|
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace Books.Api.Controllers;
|
|
|
|
[Route("api")]
|
|
[ApiController]
|
|
public class AuthController : ControllerBase
|
|
{
|
|
[HttpGet("login")]
|
|
[Authorize]
|
|
public IActionResult Login([FromQuery] string? returnUrl)
|
|
{
|
|
// The [Authorize] attribute triggers the OIDC challenge if not authenticated.
|
|
// If we reach here, the user is authenticated - redirect back to the app.
|
|
return Redirect(returnUrl ?? "/");
|
|
}
|
|
|
|
[HttpGet("logout")]
|
|
public async Task<IActionResult> Logout()
|
|
{
|
|
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
|
return Ok(new { message = "Logged out successfully" });
|
|
}
|
|
|
|
[HttpGet("profile")]
|
|
[Authorize]
|
|
public IActionResult Profile()
|
|
{
|
|
var userContext = User.GetUserContext();
|
|
if (userContext == null)
|
|
{
|
|
return Unauthorized();
|
|
}
|
|
|
|
return Ok(userContext);
|
|
}
|
|
}
|