3.8 KiB
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
"Jwt": {
"PublicKey": "<RSAKeyValue>...</RSAKeyValue>",
"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
- No local password check or token issuance — both delegated to AuthHex.
AddJwtBearervalidates issuer/audience/signature/lifetime against AuthHex's public key.OnTokenValidatedmaps the token'ssubclaim to a localUser(IUserRepository); fails the token if no matching active user exists.- On success,
username/role_id/role_levelclaims are attached from the localUser/Rolerow — authorization checks downstream are unaffected by anything AuthHex's own token claims contain. - Only one validation mechanism runs (
AddJwtBearer) — two ported alternatives exist but are not registered (see Implementation Notes). - 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/JwtMiddlewareif 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<IAuthHexClient>, 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.