--- since: 1.1.1 --- # AuthX — Security Architecture AuthX is the identity authority for DjangoPlay authentication. The security model is based on: - Password hashing - RSA-signed JWTs - Issuer and audience validation - Stateful refresh-token control - Backend service authentication - Restricted CORS origins - Strict separation of AuthX and DjangoPlay credentials - Protection of private signing keys --- ## 1. Security Architecture Overview ```mermaid flowchart TD CLIENT["Browser / API Client"] AUTH["AuthX
Identity Authority"] PASSWORD["Password Hashing
Credential Protection"] JWT["JWT Issuance
RS256"] REFRESH["Refresh Token State
Rotation / Revocation"] SERVICE["Internal Service API
X-Service-Token"] DB["AuthX PostgreSQL"] PRIVATE["RSA Private Key
JWT_PRIVATE_KEY"] PUBLIC["RSA Public Key / JWKS
JWT_PUBLIC_KEY"] POLICY["Issuer + Audience Validation
JWT_ISSUER / JWT_AUDIENCE"] CORS["CORS Origin Controls
CORS_ORIGINS"] CLIENT --> AUTH AUTH --> PASSWORD AUTH --> JWT AUTH --> REFRESH AUTH --> CORS JWT --> PRIVATE PRIVATE --> JWT JWT --> PUBLIC PUBLIC --> POLICY REFRESH --> DB PASSWORD --> DB SERVICE --> AUTH classDef client fill:#f5f3ed,stroke:#777,stroke-width:1px,color:#444 classDef auth fill:#e5f6f1,stroke:#159a7a,stroke-width:2px,color:#075d4b classDef security fill:#fceee8,stroke:#d65a2a,stroke-width:2px,color:#7d321c classDef crypto fill:#eeeeff,stroke:#5a55c9,stroke-width:2px,color:#403b91 classDef data fill:#e6f1fb,stroke:#2474b5,stroke-width:2px,color:#174d7a class CLIENT client class AUTH auth class PASSWORD,REFRESH,SERVICE,CORS,POLICY security class JWT,PRIVATE,PUBLIC crypto class DB data ```` The important security boundary is that **AuthX owns identity credentials and JWT signing**, while consuming applications only receive what they need to authenticate and verify users. --- # 2. Password Security AuthX does not store plaintext passwords. The identity record stores a password hash rather than the original password. ```text User Password │ ▼ Password Hashing │ ▼ password_hash │ ▼ AuthX PostgreSQL ``` The password credential therefore remains inside AuthX. DjangoPlay does not need to reproduce AuthX's password storage or hashing logic. AuthX is responsible for: * Receiving passwords during authentication workflows * Hashing passwords * Storing password hashes * Verifying supplied passwords against stored hashes The application database should never contain plaintext AuthX passwords. --- # 3. JWT Signing Architecture AuthX is the **sole JWT signing authority** for AuthX identity tokens. ```text signs AuthX ─────────────────────────► JWT │ │ │ JWT_PRIVATE_KEY │ │ │ │ ▼ │ DjangoPlay │ │ │ │ verifies │ ▼ └──────────────────────── AuthX Public Key ``` AuthX uses an RSA key pair and the configured signing algorithm: ```text JWT_ALGORITHM=RS256 ``` The private key is configured through: ```text JWT_PRIVATE_KEY ``` The corresponding public key is configured through: ```text JWT_PUBLIC_KEY ``` The private key belongs exclusively to AuthX. DjangoPlay and other consumers must never receive the AuthX private signing key. --- # 4. RSA Key Protection AuthX's RSA private key is one of the most sensitive credentials in the system. ```text JWT_PRIVATE_KEY │ ▼ AuthX │ │ signs ▼ JWT ``` The public key can safely be distributed to consumers for signature verification. The private key must: * Remain server-side * Never be exposed to browser clients * Never be committed to Git * Never be included in frontend assets * Never be shared with consuming applications * Be replaced if it is suspected of being compromised A fresh RSA key pair should be generated for a new deployment. Example: ```bash openssl genrsa -out jwt_private.pem 2048 openssl rsa \ -in jwt_private.pem \ -pubout \ -out jwt_public.pem ``` Validate the private key: ```bash openssl rsa -in jwt_private.pem -check -noout ``` Expected: ```text RSA key ok ``` Verify that the public key corresponds to the private key: ```bash openssl rsa \ -in jwt_private.pem \ -pubout \ -outform PEM | diff - jwt_public.pem ``` No output and exit status `0` indicate that the public and private keys match. When storing PEM values in environment configuration, preserve PEM line boundaries using literal `\n` sequences. Do not introduce arbitrary backslashes into the Base64 key material. --- # 5. JWT Claims AuthX-issued identity JWTs contain standard identity and token metadata. The important security claims include: ```text sub iss aud iat exp jti type ``` Identity access tokens additionally carry identity attributes such as: ```text email username is_verified is_staff is_superuser ``` The token type distinguishes access and refresh tokens: ```text type=access type=refresh ``` The `jti` claim provides a unique JWT identifier and is particularly important for token tracking and refresh-token state. --- # 6. Issuer Validation AuthX identifies itself as the JWT issuer using: ```text JWT_ISSUER ``` The current DjangoPlay deployment uses: ```text JWT_ISSUER=https://auth.djangoplay.org ``` The issuer identifies the **identity authority asserted by the token**. It is not necessarily the network address used to reach the AuthX process. For example: ```text AuthX runtime: APP_BASE_URL=http://127.0.0.1:8100 JWT identity authority: JWT_ISSUER=https://auth.djangoplay.org ``` These are intentionally different concepts. Consumers should validate the `iss` claim against their configured AuthX issuer. This prevents a token issued by an unexpected authority from being silently accepted. --- # 7. Audience Validation AuthX also uses: ```text JWT_AUDIENCE ``` The current DjangoPlay configuration uses: ```text JWT_AUDIENCE=djangoplay ``` The audience identifies the application for which the JWT is intended. The consumer should verify the `aud` claim as well as the signature and issuer. Conceptually: ```text JWT │ ├── Signature ──────► valid AuthX signature? │ ├── iss ────────────► expected AuthX issuer? │ └── aud ────────────► expected application? ``` A valid RSA signature alone is therefore not sufficient. --- # 8. Access Tokens and Refresh Tokens AuthX separates short-lived access tokens from longer-lived refresh tokens. The configured defaults are: ```text Access token: 60 minutes Refresh token: 30 days ``` These values are controlled by: ```text JWT_ACCESS_TOKEN_EXPIRE_MINUTES JWT_REFRESH_TOKEN_EXPIRE_DAYS ``` Access tokens are intended for normal authenticated API access. Refresh tokens are used to obtain new access tokens and therefore require additional server-side protection. --- # 9. Refresh Token State Refresh tokens are stateful. AuthX maintains refresh-token records in PostgreSQL so that refresh tokens can be controlled after issuance. Conceptually: ```text Refresh JWT │ │ jti ▼ AuthX │ ▼ Refresh Token Record │ ├── active ├── revoked └── expired ``` This allows AuthX to support server-side revocation and refresh-token lifecycle management. Access tokens, by contrast, are designed to be validated cryptographically without requiring a database lookup for every request. --- # 10. Internal Service Authentication AuthX exposes internal identity-management operations for trusted backend consumers. DjangoPlay authenticates these requests using: ```text X-Service-Token ``` The configured credential is: ```text AUTHX_SERVICE_TOKEN ``` The security boundary is: ```text DjangoPlay │ │ X-Service-Token ▼ AuthX /internal/* ``` The service token is a **backend credential**. It must never be: * Embedded in frontend JavaScript * Sent by browser clients * Stored in browser storage * Included in public documentation with its real value * Committed to source control The same service credential must be configured consistently on the DjangoPlay and AuthX sides. --- # 11. Public vs Internal API Boundary AuthX separates public authentication functionality from internal identity-management functionality. ```text AuthX │ ┌───────────┴───────────┐ │ │ ▼ ▼ Public Authentication Internal APIs │ │ │ │ X-Service-Token ▼ ▼ Browser / Clients DjangoPlay ``` Public clients should not receive the credentials required to access internal identity-management endpoints. The internal service token establishes a backend-to-backend trust boundary. --- # 12. CORS Protection AuthX supports explicit browser-origin configuration through: ```text CORS_ORIGINS ``` Development may contain local DjangoPlay origins such as: ```text https://app.lvh.me:9999 http://app.lvh.me:3333 ``` Production should contain only the browser origins that actually need to communicate with AuthX. Example production configuration: ```text CORS_ORIGINS=https://app.djangoplay.org,https://issues.djangoplay.org ``` CORS configuration should not be treated as authentication. It controls which browser origins may make cross-origin requests; it does not replace JWT validation or backend service authentication. Do not unnecessarily add: ```text * ``` or unrelated production origins. --- # 13. Credential Separation DjangoPlay and AuthX deliberately maintain separate credentials. ```text DjangoPlay │ ├── DB_PASSWORD ├── DATABASE_URL └── JWT_SIGNING_KEY ``` versus: ```text AuthX │ ├── POSTGRES_PASSWORD ├── DATABASE_URL ├── DATABASE_URL_SYNC ├── JWT_PRIVATE_KEY ├── JWT_PUBLIC_KEY ├── JWT_ISSUER ├── JWT_AUDIENCE └── AUTHX_SERVICE_TOKEN ``` These credentials must not be confused. In particular: ```text DB_PASSWORD != POSTGRES_PASSWORD JWT_SIGNING_KEY != JWT_PRIVATE_KEY APP_BASE_URL != JWT_ISSUER AUTHX_SERVICE_TOKEN != database password ``` The current configuration explicitly uses `POSTGRES_PASSWORD` for the AuthX PostgreSQL role; there is no `AUTHX_DB_PASSWORD` configuration key. --- # 14. AuthX Configuration Boundary For a DjangoPlay deployment, AuthX configuration is maintained separately from the main DjangoPlay secrets. ```text ~/.dplay/ │ ├── .secrets │ │ │ └── DjangoPlay secrets │ └── .authx │ └── AuthX configuration ``` The AuthX configuration contains its own: * Database credentials * JWT signing keys * JWT configuration * Service authentication credential * CORS configuration This separation reduces accidental credential mixing between the two systems. --- # 15. Database Security AuthX stores identity data in its own PostgreSQL database. The database contains sensitive identity information, including password hashes and refresh-token state. The AuthX PostgreSQL credentials are: ```text POSTGRES_USER POSTGRES_PASSWORD POSTGRES_DB ``` and the corresponding connection strings: ```text DATABASE_URL DATABASE_URL_SYNC ``` The AuthX database credentials are separate from DjangoPlay's database credentials. Database access should therefore remain restricted to the AuthX runtime and trusted administrative operations. --- # 16. Security Boundary Summary ```mermaid flowchart LR CLIENT["Public Client"] AUTHX["AuthX"] INTERNAL["Trusted Backend
DjangoPlay"] DB["AuthX PostgreSQL"] KEYS["RSA Key Material"] CLIENT -->|"Authentication"| AUTHX INTERNAL -->|"X-Service-Token"| AUTHX AUTHX -->|"Read / write identity data"| DB AUTHX -->|"JWT_PRIVATE_KEY
signs tokens"| KEYS KEYS -->|"JWT_PUBLIC_KEY / JWKS
verification"| CLIENT KEYS -->|"Public-key verification"| INTERNAL classDef client fill:#f5f3ed,stroke:#777,stroke-width:1px,color:#444 classDef auth fill:#e5f6f1,stroke:#159a7a,stroke-width:2px,color:#075d4b classDef trusted fill:#eeeeff,stroke:#5a55c9,stroke-width:2px,color:#403b91 classDef data fill:#e6f1fb,stroke:#2474b5,stroke-width:2px,color:#174d7a class CLIENT client class AUTHX auth class INTERNAL,KEYS trusted class DB data ``` The major trust boundaries are: | Boundary | Protection | | ------------------- | ------------------------------------------ | | Client → AuthX | Authentication protocol and token issuance | | Backend → AuthX | `X-Service-Token` | | AuthX → PostgreSQL | Restricted database credentials | | AuthX → JWT | RSA private-key signing | | Consumer → JWT | Public-key signature verification | | JWT → Consumer | Issuer and audience validation | | Browser → AuthX | Explicit CORS origins | | AuthX configuration | Separate `.authx` deployment configuration | --- # 17. Security Rules The following rules should be treated as mandatory: 1. Never store plaintext passwords. 2. Never expose `JWT_PRIVATE_KEY`. 3. Never expose `AUTHX_SERVICE_TOKEN` to browser clients. 4. Never commit private keys or service credentials to Git. 5. Use `RS256` for the current AuthX JWT architecture. 6. Validate JWT signature, issuer, audience, expiration, and token type as appropriate for the operation. 7. Keep AuthX database credentials separate from DjangoPlay credentials. 8. Use explicit production CORS origins. 9. Generate a fresh RSA key pair for a new deployment. 10. Rotate compromised private keys and service tokens. 11. Keep AuthX identity persistence inside AuthX rather than duplicating credential-management logic in DjangoPlay. 12. Treat AuthX as the identity and JWT-signing authority. --- # 18. Security Responsibilities The security responsibilities are deliberately divided between AuthX and its consumers. ### AuthX AuthX is responsible for: * Password credential protection * Identity persistence * Password verification * JWT signing * Refresh-token state * JWT issuer and audience configuration * Internal service authentication * CORS configuration * Protection of private signing keys ### DjangoPlay DjangoPlay is responsible for: * Securely storing its AuthX service credential * Verifying AuthX-issued JWTs * Configuring the expected issuer and audience * Protecting local application sessions and credentials * Enforcing application-level authorization * Never exposing AuthX private credentials This separation ensures that authentication authority and application-level authorization remain distinct concerns. --- # 19. Security Model Summary AuthX's security model can be summarized as: ```text AUTHX │ ┌─────────┼─────────┐ │ │ │ ▼ ▼ ▼ Credentials JWTs Internal API │ │ │ ▼ ▼ ▼ Hashing RS256 Service Token │ ┌───────┴───────┐ │ │ ▼ ▼ Private Key Public Key signing verification │ │ ▼ ▼ AuthX Consumers ``` AuthX therefore provides a dedicated identity security boundary: credentials and signing authority remain inside AuthX, while DjangoPlay and other consumers verify and consume the resulting identity assertions without receiving AuthX's most sensitive secrets.