Skip to content

Auth Service

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]
FieldTypeDescription
idUUID (PK)Internal auth identity
emailCITEXT UNIQUEUnique email (nullable if phone-only)
phone_e164VARCHAR(20) UNIQUEE.164 phone (nullable if email-only)
password_hashTEXTHashed password (bcrypt / argon2id)
statusENUM(active, suspended, deleted)Account lifecycle state
failed_attemptsSMALLINTConsecutive failed logins (reset on success)
locked_untilTIMESTAMPTZLockout expiry (NULL = not locked)
email_verifiedBOOLEANEmail verification status
phone_verifiedBOOLEANPhone verification status
created_atTIMESTAMPTZAccount creation time
updated_atTIMESTAMPTZLast modification time
deleted_atTIMESTAMPTZSoft-delete timestamp
FieldTypeDescription
idUUID (PK)Session identifier
account_idUUID (FK)References auth_accounts
refresh_token_hashTEXTSHA-256 hash of refresh token
device_fingerprintTEXTDevice identifier string
ip_addrINETLogin IP address
user_agentTEXTBrowser/client user agent
expires_atTIMESTAMPTZRefresh token expiry
revoked_atTIMESTAMPTZNULL until explicitly revoked
created_atTIMESTAMPTZSession creation time
FieldTypeDescription
jtiUUID (PK)JWT ID of revoked token
account_idUUIDOwner of revoked token
expires_atTIMESTAMPTZOriginal token expiry (for TTL cleanup)
created_atTIMESTAMPTZRevocation time
FieldTypeDescription
idUUID (PK)Factor identifier
account_idUUID (FK)References auth_accounts
factor_typeENUM(totp, webauthn, sms, email)MFA method
secret_encBYTEAEncrypted TOTP secret or WebAuthn credential
is_primaryBOOLEANWhether this is the primary MFA factor
verified_atTIMESTAMPTZWhen factor was verified
created_atTIMESTAMPTZCreation time
FieldTypeDescription
kidTEXT (PK)Key ID published in JWKS
algorithmTEXTRS256, ES256, or EdDSA
public_key_pemTEXTPublic key (published via JWKS)
private_key_encBYTEAEncrypted private key (KMS-wrapped)
statusENUM(active, rotated, revoked)Key lifecycle state
activated_atTIMESTAMPTZWhen key became active
rotated_atTIMESTAMPTZWhen key was rotated
FieldTypeDescription
idBIGSERIALAuto-incrementing audit ID
account_idUUIDAccount involved
actionTEXTsignup, login.success, login.fail, logout, mfa.enroll, etc.
ip_addrINETClient IP
user_agentTEXTClient user agent
metadataJSONBExtra context (device, geo, failure reason)
created_atTIMESTAMPTZEvent timestamp
Key PatternPurposeTTL
auth:otp:{account_id}:{channel}Pending OTP code5 min
auth:lockout:{account_id}Account lockout markerConfigurable (e.g., 15 min)
auth:rate:{ip}IP-based rate limiterSliding window
auth:session:count:{account_id}Active session countSynced with DB
MethodEndpointDescription
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
MethodEndpointDescription
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
MethodEndpointDescription
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)",
"aud": "tradex",
"iss": "auth.tradex",
"iat": 1700000000,
"exp": 1700001800,
"jti": "unique-token-id",
"email": "user@example.com",
"email_verified": true,
"mfa_verified": true
}
  • 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
TopicKey FieldsWhenConsumers
auth.created.v1auth_id, email, phoneOn signupUser Service
auth.login.v1auth_id, ip, deviceOn successful loginUser Service, Analytics
auth.account.activated.v1auth_idOn email/phone verificationUser Service
auth.account.deactivated.v1auth_id, reasonOn suspensionUser Service, Wallet, Risk
auth.account.deleted.v1auth_idOn soft deleteUser Service, Wallet
  1. Client → POST /v1/auth/signup { email, password }
  2. Validate email format, password strength (min 8 chars, complexity rules)
  3. Hash password with bcrypt (or argon2id)
  4. Insert into auth_accounts (status = active, email_verified = false)
  5. Generate OTP → store in Redis auth:otp:{id}:email (TTL 5 min)
  6. Send verification email via Notification Service
  7. Emit auth.created.v1 to Kafka
  8. Return { auth_id, message: "Verification email sent" }
  1. Client → POST /v1/auth/login { email, password, device_fingerprint }
  2. Lookup account by email → check status = active and not locked
  3. Verify password hash
  4. If failed: increment failed_attempts, lock if threshold exceeded
  5. If MFA enrolled: return { mfa_required: true, mfa_token } → client submits TOTP code
  6. Generate access + refresh token pair (sign with active signing_keys entry)
  7. Create auth_sessions row (hash refresh token)
  8. Enforce device limit: if too many sessions, revoke oldest
  9. Emit auth.login.v1
  10. Return { access_token, refresh_token, expires_in }
  1. Client → POST /v1/auth/refresh { refresh_token }
  2. Hash token → lookup in auth_sessions
  3. Validate: not revoked, not expired, device fingerprint matches
  4. Issue new access token + new refresh token (rotate)
  5. Update auth_sessions with new refresh token hash
  6. Return { access_token, refresh_token, expires_in }
  1. Client → POST /v1/auth/logout (with access token)
  2. Revoke the session (set revoked_at)
  3. Add access token jti to jwt_blacklist (TTL = remaining access token lifetime)
  4. 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
ScenarioHandling
Redis outageDegrade gracefully: OTP delivery paused, rate limiting disabled (fail-open with stricter server-side checks)
DB outageReturn 503; no auth operations possible. Circuit breaker prevents cascading
Kafka publish failureTransactional outbox pattern: write event to local outbox table, relay worker publishes to Kafka
Key rotation failureKeep current key active; alert ops; retry
Notification service downOTP stored in Redis awaiting delivery; retry with exponential backoff
MetricTarget
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)
VariableDescriptionDefault
POSTGRES_URLPostgreSQL connection stringRequired
REDIS_URLRedis connection stringRequired
KAFKA_BROKERSComma-separated Kafka brokersRequired
JWKS_ALGORITHMJWT signing algorithmRS256
ACCESS_TOKEN_EXPIRE_MINUTESAccess token TTL30
REFRESH_TOKEN_EXPIRE_DAYSRefresh token TTL7
MAX_SESSIONS_PER_ACCOUNTDevice session limit5
LOCKOUT_THRESHOLDFailed attempts before lockout5
LOCKOUT_DURATION_MINUTESLockout duration15
OTP_TTL_SECONDSOTP expiration300
  • 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