103 lines
3.8 KiB
Markdown
103 lines
3.8 KiB
Markdown
# 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": "<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
|
|
|
|
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<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.
|