# Authentication — Own-User Auth via External Identity Service (AuthHex) **Status**: Done **Depends On**: — (cross-cutting infrastructure, not a numbered feature step) --- ## Goal Own-user (staff/API) authentication is delegated to an external identity service ("AuthHex"), which owns credentials and signs RS256 JWTs. This app validates those tokens and maps them onto its own `User`/`Role` tables so the existing permission-policy authorization stays unchanged. --- ## Background / Context Previously this app verified passwords and minted its own HMAC-signed JWT locally. That's been replaced — AuthHex now owns credentials and signing; this app only holds AuthHex's **public** key to validate tokens, and never sees a plaintext password for this flow. Authorization is untouched: permission policies, role levels, scope checks, and the seeded role set all still work exactly as before, because every validated token is mapped back onto a local `User`/`Role` row before any authorization check runs. --- ## Configuration Changes ```jsonc "Jwt": { "PublicKey": "...", "Audience": "AuthHexClient", "Issuer": "AuthHex" }, "AuthHex": { "BaseUrl": "http://localhost:6102" } ``` `Jwt:Key` (old symmetric secret) is removed. `Jwt:PublicKey` is AuthHex's RSA public key in XML format (not PEM). --- ## API Endpoints | Method | Route | Notes | |---|---|---| | POST | /api/auth/login | Proxies to AuthHex `loginUser` | | POST | /api/auth/register | Proxies to AuthHex `registerUser` | | GET | /api/auth/me | `[Authorize]`, proxies to AuthHex `getUserDetails` | | POST | /api/auth/logout | `[Authorize]`, proxies to AuthHex `LogoutUser` | All four just forward to `POST {AuthHex:BaseUrl}/api/user` and pass AuthHex's response back as-is. No cookie — token returned in the response body, sent back as `Authorization: Bearer`. --- ## Business Rules 1. No local password check or token issuance — both delegated to AuthHex. 2. `AddJwtBearer` validates issuer/audience/signature/lifetime against AuthHex's public key. 3. `OnTokenValidated` maps the token's `sub` claim to a local `User` (`IUserRepository`); fails the token if no matching active user exists. 4. On success, `username`/`role_id`/`role_level` claims are attached from the **local** `User`/ `Role` row — authorization checks downstream are unaffected by anything AuthHex's own token claims contain. 5. Only one validation mechanism runs (`AddJwtBearer`) — two ported alternatives exist but are not registered (see Implementation Notes). 6. No refresh tokens — once the AuthHex-issued token expires, the client must log in again. --- ## Permissions to Add to Seed None — this only changes authentication. Permission policies and role seed data are untouched. --- ## Open Questions - Add a refresh-token flow if AuthHex doesn't offer one and re-login UX proves disruptive. - Keep or delete the ported `AuthMiddleware`/`JwtMiddleware` if never actually needed. - No retry/circuit-breaker if AuthHex is unreachable. --- ## Implementation Notes ``` Features/Auth/ — AuthController, AuthService, AuthDTOs (thin AuthHex proxies) Infra/Authorization/ AuthHex/ — IAuthHexClient, AuthHexClient, AuthHexResponse Jwt/RsaXmlKeyReader.cs — portable XML→RSA key parser (RSA.FromXmlString is Windows-only) Middleware/ — AuthMiddleware, JwtMiddleware, JwtOptions, ApiResponse (ported from OSMCore for reference — NOT registered) Program.cs — AddHttpClient, AddJwtBearer + OnTokenValidated (maps token → local User/Role → role_id/role_level claims) ``` **Gotcha:** don't run more than one of `AddJwtBearer` / `AuthMiddleware` / `JwtMiddleware.CustomMiddlewareAttribute` at once — each re-validates the same token independently.