---
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.