diff --git a/Distribution/docs/authentication_flow.md b/Distribution/docs/authentication_flow.md
new file mode 100644
index 0000000..ecddb8c
--- /dev/null
+++ b/Distribution/docs/authentication_flow.md
@@ -0,0 +1,102 @@
+# 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.