The Auth Service is a FastAPI (Python) microservice that acts as the sole credential authority for the TradeX platform. It owns the signup → login → token lifecycle and is the single issuer of JWTs consumed by all downstream services.
Credential Authority : Owns email/phone + password registration and verification. Only service that stores hashed credentials.
Token Issuer : Issues short-lived access tokens and long-lived refresh tokens (asymmetric signing). Publishes a JWKS endpoint for all services to verify tokens without calling Auth.
Session Management : Tracks active refresh tokens per device, enforces session limits, and supports forced logout.
MFA / OTP : TOTP-based second factor, OTP via SMS/email for passwordless flows.
Key Management : Manages signing key pairs (rotation, JWKS publication).
Event Producer : Emits auth.created, auth.login, auth.account.* events to Kafka for downstream consumption.
Policy Enforcement : Account lockout after N failures, device limits, IP-based rate limiting.
Auth does not own user profile data, KYC, or preferences — those belong to the User Service .
graph TB
Client[Client App] -->|REST| Gateway[API Gateway]
Gateway -->|JWT Verify via JWKS| Auth[Auth Service]
Auth -->|Stores| PG[(PostgreSQL)]
Auth -->|Session Cache| Redis[(Redis)]
Auth -->|Events| Kafka[Kafka Bus]
Kafka --> UserSvc[User Service]
Kafka --> WalletSvc[Wallet Service]
Auth -->|JWKS| AllServices[All Services]
Field Type Description idUUID (PK) Internal auth identity emailCITEXT UNIQUE Unique email (nullable if phone-only) phone_e164VARCHAR(20) UNIQUE E.164 phone (nullable if email-only) password_hashTEXT Hashed password (bcrypt / argon2id) statusENUM(active, suspended, deleted) Account lifecycle state failed_attemptsSMALLINT Consecutive failed logins (reset on success) locked_untilTIMESTAMPTZ Lockout expiry (NULL = not locked) email_verifiedBOOLEAN Email verification status phone_verifiedBOOLEAN Phone verification status created_atTIMESTAMPTZ Account creation time updated_atTIMESTAMPTZ Last modification time deleted_atTIMESTAMPTZ Soft-delete timestamp
Field Type Description idUUID (PK) Session identifier account_idUUID (FK) References auth_accounts refresh_token_hashTEXT SHA-256 hash of refresh token device_fingerprintTEXT Device identifier string ip_addrINET Login IP address user_agentTEXT Browser/client user agent expires_atTIMESTAMPTZ Refresh token expiry revoked_atTIMESTAMPTZ NULL until explicitly revoked created_atTIMESTAMPTZ Session creation time
Field Type Description jtiUUID (PK) JWT ID of revoked token account_idUUID Owner of revoked token expires_atTIMESTAMPTZ Original token expiry (for TTL cleanup) created_atTIMESTAMPTZ Revocation time
Field Type Description idUUID (PK) Factor identifier account_idUUID (FK) References auth_accounts factor_typeENUM(totp, webauthn, sms, email) MFA method secret_encBYTEA Encrypted TOTP secret or WebAuthn credential is_primaryBOOLEAN Whether this is the primary MFA factor verified_atTIMESTAMPTZ When factor was verified created_atTIMESTAMPTZ Creation time
Field Type Description kidTEXT (PK) Key ID published in JWKS algorithmTEXT RS256, ES256, or EdDSApublic_key_pemTEXT Public key (published via JWKS) private_key_encBYTEA Encrypted private key (KMS-wrapped) statusENUM(active, rotated, revoked) Key lifecycle state activated_atTIMESTAMPTZ When key became active rotated_atTIMESTAMPTZ When key was rotated
Field Type Description idBIGSERIAL Auto-incrementing audit ID account_idUUID Account involved actionTEXT signup, login.success, login.fail, logout, mfa.enroll, etc.ip_addrINET Client IP user_agentTEXT Client user agent metadataJSONB Extra context (device, geo, failure reason) created_atTIMESTAMPTZ Event timestamp
Key Pattern Purpose TTL auth:otp:{account_id}:{channel}Pending OTP code 5 min auth:lockout:{account_id}Account lockout marker Configurable (e.g., 15 min) auth:rate:{ip}IP-based rate limiter Sliding window auth:session:count:{account_id}Active session count Synced with DB
Method Endpoint Description POST/v1/auth/signupRegister with email/phone + password POST/v1/auth/loginAuthenticate and receive token pair POST/v1/auth/verify-emailVerify email with OTP/link token POST/v1/auth/verify-phoneVerify phone with OTP POST/v1/auth/forgot-passwordInitiate password reset POST/v1/auth/reset-passwordComplete password reset with token POST/v1/auth/refreshExchange refresh token for new access token GET/.well-known/jwks.jsonJWKS endpoint for token verification
Method Endpoint Description GET/v1/auth/sessionsList active sessions DELETE/v1/auth/sessions/{id}Revoke specific session POST/v1/auth/logoutRevoke current session POST/v1/auth/logout-allRevoke all sessions POST/v1/auth/change-passwordChange password (requires current password) POST/v1/auth/mfa/enrollBegin MFA enrollment POST/v1/auth/mfa/verifyComplete MFA enrollment with code DELETE/v1/auth/mfaRemove MFA factor
Method Endpoint Description GET/internal/auth/accounts/{id}Get account details POST/internal/auth/accounts/{id}/suspendSuspend account POST/internal/auth/accounts/{id}/reactivateReactivate account POST/internal/auth/keys/rotateRotate signing keys GET/internal/auth/keysList all signing keys
"sub" : " auth_account_id (UUID) " ,
"jti" : " unique-token-id " ,
"email" : " user@example.com " ,
Lifetime : 15–30 minutes (configurable)
Signing : Asymmetric (RS256 / ES256 / EdDSA via signing_keys)
Verification : Any service can verify via JWKS endpoint — no network call to Auth needed
Lifetime : 7–30 days (configurable)
Storage : SHA-256 hash stored in auth_sessions
Rotation : Each refresh issues a new refresh token and invalidates the old one
Binding : Bound to device fingerprint — reuse from different device triggers revocation of all sessions
Topic Key Fields When Consumers auth.created.v1auth_id, email, phoneOn signup User Service auth.login.v1auth_id, ip, deviceOn successful login User Service, Analytics auth.account.activated.v1auth_idOn email/phone verification User Service auth.account.deactivated.v1auth_id, reasonOn suspension User Service, Wallet, Risk auth.account.deleted.v1auth_idOn soft delete User Service, Wallet
Client → POST /v1/auth/signup { email, password }
Validate email format, password strength (min 8 chars, complexity rules)
Hash password with bcrypt (or argon2id)
Insert into auth_accounts (status = active, email_verified = false)
Generate OTP → store in Redis auth:otp:{id}:email (TTL 5 min)
Send verification email via Notification Service
Emit auth.created.v1 to Kafka
Return { auth_id, message: "Verification email sent" }
Client → POST /v1/auth/login { email, password, device_fingerprint }
Lookup account by email → check status = active and not locked
Verify password hash
If failed: increment failed_attempts, lock if threshold exceeded
If MFA enrolled: return { mfa_required: true, mfa_token } → client submits TOTP code
Generate access + refresh token pair (sign with active signing_keys entry)
Create auth_sessions row (hash refresh token)
Enforce device limit: if too many sessions, revoke oldest
Emit auth.login.v1
Return { access_token, refresh_token, expires_in }
Client → POST /v1/auth/refresh { refresh_token }
Hash token → lookup in auth_sessions
Validate: not revoked, not expired, device fingerprint matches
Issue new access token + new refresh token (rotate)
Update auth_sessions with new refresh token hash
Return { access_token, refresh_token, expires_in }
Client → POST /v1/auth/logout (with access token)
Revoke the session (set revoked_at)
Add access token jti to jwt_blacklist (TTL = remaining access token lifetime)
Return 204 No Content
Account creation : UNIQUE constraint on email and phone_e164 prevents duplicates
Session limits : Per-account session count enforced via Redis counter + DB check
Token refresh : SELECT ... FOR UPDATE on session row prevents concurrent refresh races
Password changes : Require current password verification — invalidates all other sessions
Key rotation : New key activated → old key marked rotated → old key still valid for verification (grace period) → eventually revoked
Email : RFC 5322 format, normalized lowercase
Phone : E.164 format validation
Password : Minimum 8 characters, at least 1 uppercase, 1 lowercase, 1 digit
OTP : 6-digit numeric, expires after 5 minutes, max 3 attempts
Rate limits : 5 login attempts per minute per IP; 10 OTP requests per hour per account
Lockout : Account locked after 5 consecutive failed attempts (configurable, 15-minute default)
auth_signup_total{status} — Signup attempts (success/failure)
auth_login_total{status,method} — Login attempts by method
auth_token_refresh_total{status} — Token refresh operations
auth_mfa_verify_total{status} — MFA verification attempts
auth_account_locked_total — Account lockout events
auth_session_active_gauge — Active sessions count
Spans: auth.signup, auth.login, auth.verify_password, auth.generate_tokens, auth.refresh, auth.mfa.verify
Structured JSON with fields: account_id, action, ip, user_agent, status, trace_id. PII (email, phone) is masked in logs.
Password Hashing : bcrypt with work factor 12 (target: argon2id with memory=64MB, iterations=3, parallelism=4)
Token Signing : Asymmetric keys (RS256 currently; target: EdDSA/ES256). Private keys encrypted at rest with KMS.
JWKS Publication : /.well-known/jwks.json — eliminates need for shared secrets across services
Refresh Token Binding : Tied to device fingerprint; cross-device reuse triggers full session revocation
Anti-Replay : OTP nonces tracked in Redis; refresh tokens rotated on each use
Transport : TLS 1.2+ required for all endpoints
Internal Auth : mTLS for service-to-service calls; internal JWT with aud=admin scope for admin endpoints
Scenario Handling Redis outage Degrade gracefully: OTP delivery paused, rate limiting disabled (fail-open with stricter server-side checks) DB outage Return 503; no auth operations possible. Circuit breaker prevents cascading Kafka publish failure Transactional outbox pattern: write event to local outbox table, relay worker publishes to Kafka Key rotation failure Keep current key active; alert ops; retry Notification service down OTP stored in Redis awaiting delivery; retry with exponential backoff
Metric Target Login latency (p95) < 200 ms Token refresh latency (p95) < 100 ms Signup success rate ≥ 99.5% JWKS endpoint availability ≥ 99.99% Event delivery to Kafka ≥ 99.9% (with outbox)
Variable Description Default POSTGRES_URLPostgreSQL connection string Required REDIS_URLRedis connection string Required KAFKA_BROKERSComma-separated Kafka brokers Required JWKS_ALGORITHMJWT signing algorithm RS256ACCESS_TOKEN_EXPIRE_MINUTESAccess token TTL 30 REFRESH_TOKEN_EXPIRE_DAYSRefresh token TTL 7 MAX_SESSIONS_PER_ACCOUNTDevice session limit 5 LOCKOUT_THRESHOLDFailed attempts before lockout 5 LOCKOUT_DURATION_MINUTESLockout duration 15 OTP_TTL_SECONDSOTP expiration 300
Language : Python 3.10+
Framework : FastAPI + Uvicorn
ORM : SQLAlchemy + Alembic migrations
Password Hashing : passlib with bcrypt
JWT : python-jose / PyJWT
Kafka : confluent-kafka-python with Avro serialization
Package Manager : uv