gh_UserManager/docs/SECURITY.md
bermooda-company 54d5891edf user
2026-08-23 23:59:14 +03:30

8.1 KiB

Security Policy

Overview

The Identity Platform implements production-grade security without false claims. This document describes the security model, controls, and operational requirements.

Cryptographic Design

Asymmetric Signing (RS256)

  • Algorithm: RS256 (RSA Signature with SHA-256)
  • Private key: Held ONLY by the Identity Platform (used for signing tokens)
  • Public key: Distributed via JWKS endpoint (/.well-known/jwks.json)
  • Products verify tokens using the public key — they cannot forge tokens
  • Private key location: Environment-defined in development (SIMPLE_JWT_SIGNING_KEY), mounted volume/file in production

Key properties:

  • Products never have the signing key
  • Adding a new product requires no key distribution — just JWKS discovery
  • Key rotation is supported with an overlap window

Password Storage

  • Backend: PBKDF2 with HMAC-SHA256 (Django default)
  • Iterations: 722,768 (OWASP-recommended as of 2024)
  • Salt: Per-password random salt via Django's password hasher
  • Future: Migration path to Argon2id

Key Rotation (Planned)

  • JWKS endpoint returns the current public key(s)
  • Multiple keys can be published with kid header support
  • Overlap window allows old tokens to be validated during rotation
  • Private key rotated via operational process (not code change)

Token Security

Access Token

  • Algorithm: RS256 (JWT)
  • Lifetime: 15 minutes (configurable)
  • Claims: user_id, user_status, identity_key, optional product_key, optional organization_id, exp, iat, iss, jti
  • Storage: In-memory on client (NOT in localStorage); server never sees raw access token again after issuance

Refresh Token

  • Algorithm: RS256 (JWT)
  • Lifetime: 30 days (configurable)
  • Storage: Hashed in database (not reversible)
  • Rotation: New refresh token issued on each use; old one revoked with replaced_by set
  • Revocation: Instant via /auth/logout (blacklist + session revoke)

Token Blacklist

  • Mechanism: token_blacklist app with TokenBlacklist model
  • When added: Logout, password change, password reset, session revocation
  • Check: Middleware validates every incoming access token against the blacklist
  • Cleanup: Background job removes expired blacklist entries (cron: python manage.py shell — cleanup script)

Brute-force Protection

  • Login rate limit: 5 attempts per 5 minutes per IP per email
  • Auto-lockout: After threshold exceeded → login_locked security event (severity: high)
  • Unlock: Time-based (15 min) or admin override

Network & API Security

CORS

  • Config: CORS_ALLOWED_ORIGINS (environment-configured)
  • Credentials: CORS_ALLOW_CREDENTIALS=True (cookies for browser sessions)
  • Default: http://localhost:3000, http://127.0.0.1:3000

CSRF

  • API: JWT Bearer tokens are immune to CSRF (no cookies for auth)
  • Django Session: CSRF middleware active for admin interface
  • Trusted origins: CSRF_TRUSTED_ORIGINS environment-configured

Rate Limiting

Endpoint Limit Scope
/v1/auth/login 5/min per IP + per email
/v1/auth/refresh 20/min per user
/v1/auth/logout 10/min per user
/auth/password/reset 3/hour per IP
All API endpoints 100/min per authenticated user
Unauthenticated access 20/min per IP

Rate limiter uses Redis cache (LocMemCache in dev). Key format: {endpoint}:{identifier}:{window}.

Audit & Monitoring

Security Events

Every security-relevant action creates an immutable SecurityEvent:

Action Event Type Severity
Successful login login_success info
Failed login login_failed medium
Account locked login_locked high
Logout logout low
Password changed password_change medium
Password reset password_reset medium
Email verified email_verified low
Phone verified phone_verified low
MFA enrolled mfa_enabled low
MFA disabled mfa_disabled high
Session revoked session_revoked medium
Token refreshed token_refreshed low

Events are immutable — no DELETE/UPDATE allowed via API. They are append-only audit records.

Event Retention

  • Events: 2 years (compliance)
  • Sessions: 90 days after expiration (debugging)
  • Tokens: 30 days after expiry (token chain reconstruction)
  • Blacklist entries: 15 minutes after access token expiry

Service-to-Service Authentication

Current (v1.0): API Key

  • Products authenticate with X-API-Key header
  • API Key = client_id:client_secret (from Application registration)
  • Validated against Application model (status=active)
  • Rate limited at service level

Future (v2.0): OAuth2 Client Credentials

  • Products use grant_type=client_credentials
  • Platform issues service-to-service JWT signed with RS256
  • Service JWTs have scope claim (audience-scoped)
  • mTLS as transport layer option

Secret Management

Secrets in Repository: NEVER

  • No secrets committed: .env files are gitignored
  • Private key: NOT in repository. Loaded from env or mounted file in production
  • Database password: Environment (DATABASE_URL)
  • Redis password: Environment (REDIS_URL)

Environment Configuration

# Required in production
DJANGO_SECRET_KEY=<64-char random string>
DATABASE_URL=postgres://user:pass@postgres:5432/identity
REDIS_URL=redis://redis:6379/0

# JWT signing (production: load from mounted file)
DJANGO_SIGNING_KEY=<RSA private key PEM>

# CORS
CORS_ALLOWED_ORIGINS=https://bermooda.example.com,https://hamsoo.example.com
CSRF_TRUSTED_ORIGINS=https://id.example.com

Development

  • DATABASE_URL=sqlite:///dev.db for local dev
  • DJANGO_DEBUG=true for local dev
  • DJANGO_ENV=production enforces DJANGO_SECRET_KEY must be set (no dev fallback)

MFA (Multi-Factor Authentication)

Current (v1.0)

  • TOTP: totp_secret field on User, mfa_enabled flag
  • Verification: 6-digit codes via RFC 6238
  • Recovery: Not yet implemented (TODO)

Planned (v1.2)

  • Backup codes: 10 single-use codes
  • WebAuthn/Passkeys: Platform authenticator support
  • MFA policies: Per-organization enforcement
  • Step-up auth: Re-auth for sensitive actions (org deletion, secret rotation)

Threat Model

Threat Mitigation
Token forgery RS256 — products can't forge without private key
Token replay Short 15-min access tokens; refresh token rotation
Session hijacking Session tied to IP+UA fingerprint; user-agent mismatch detection
Brute force Rate limiting + auto-lockout
Account takeover Password breach check (planned); MFA (available)
Privilege escalation Contextual roles (org-level, not global); product-layer roles
Data leakage No product-domain data in identity; separate databases/services
Insider threat Audit log (immutable); admin actions all logged
CSRF JWT Bearer tokens (no cookies for auth)
DoS Rate limiting; pagination; health checks

Security Operations

Incident Response

  1. Detection: SecurityEvent with severity HIGH/CRITICAL → alert
  2. Triage: Admin reviews event metadata (IP, UA, user, context)
  3. Response: Revoke session, reset password, disable MFA, blacklist tokens
  4. Investigation: Full audit log available via /admin/ and /v1/security/events/
  5. Post-mortem: Documented with root cause analysis

Security Headers

  • Content-Security-Policy: Default-deny
  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Strict-Transport-Security: max-age=31536000; includeSubDomains
  • Referrer-Policy: strict-origin-when-cross-origin

(These are handled by Django middleware + nginx in production)

Security Testing

  • JWT signature verification (RS256)
  • Token blacklist on logout
  • Token blacklist on password change
  • Rate limiting on login
  • Brute-force lockout
  • Session revocation
  • Password complexity validation (min 10 chars)
  • JWT payload contains no sensitive data
  • Private key not accessible from Products
  • All security events logged with IP + UA