Authentication Architecture
Vidivo uses asymmetric JWT tokens (RS256) for authentication, with refresh token rotation for long-lived sessions. The system supports email/password login, Google OAuth, Apple Sign-In, and anonymous guest sessions.
Token Architecture
Section titled “Token Architecture”Access Tokens
Section titled “Access Tokens”- Algorithm: RS256 (RSA Signature with SHA-256)
- TTL: 15 minutes
- Signing: Private RSA key held only by the auth service
- Validation: Public key available at
/.well-known/jwks.json
Because access tokens are verified using a public key, any service can validate tokens without communicating with the auth service. This eliminates the auth service as a bottleneck.
Refresh Tokens
Section titled “Refresh Tokens”- TTL: 30 days
- Storage: Redis (server-side state)
- Rotation: Every use produces a new refresh token and invalidates the old one
- Replay detection: If a used refresh token is presented again, all sessions for that user are invalidated
Token Claims
Section titled “Token Claims”{ "sub": "01JN2X4K8M3F7QPZRVWT6YBHCE", "email": "jane@example.com", "role": "host", "iat": 1710000000, "exp": 1710000900, "iss": "api.vidivo.app"}Authentication Flow
Section titled “Authentication Flow” Client API Gateway Auth Service | | | |-- POST /auth/login ------>| | | |-- Forward to auth svc ->| | | | | | Verify password | | | (bcrypt cost 12) | | | | | | Generate tokens | | | Store refresh in | | | Redis | | | | |<-- access_token ----------|<-- tokens --------------| | refresh_token | | | expires_in: 900 | | | | | |-- GET /users/me --------->| | | Authorization: Bearer | | | | | | Validate JWT | | (public key) | | Check expiry | | Extract claims | | | | |<-- user profile ----------| |Refresh Token Rotation
Section titled “Refresh Token Rotation”Refresh token rotation is a security mechanism that limits the window of opportunity for token theft:
Client Redis | | | 1. POST /auth/refresh | | { refresh_token: "rt_old" } | | | | 2. Lookup rt_old in Redis ------>| | | | Found + not used: | | - Mark rt_old as used | | - Generate rt_new | | - Store rt_new in Redis | | | | 3. Return new tokens <-----------| | { access_token, refresh_token: "rt_new" } | | | If rt_old is presented again: | | - REPLAY DETECTED | | - Invalidate ALL sessions | | for this user | | - Return 401 |OAuth Integration
Section titled “OAuth Integration”Google OAuth
Section titled “Google OAuth”- Client obtains a Google ID token using the Google Sign-In SDK
- Client sends the ID token to
POST /v1/auth/google - Server validates the token using
google.golang.org/api/idtoken.Validate()with the configured client ID - Server extracts the email and Google user ID from the token claims
- User resolution:
- If a user exists with the Google provider ID: log them in
- If a user exists with the same email: link the Google account
- Otherwise: create a new user
Apple Sign-In
Section titled “Apple Sign-In”- Client obtains an Apple identity token using Apple’s AuthenticationServices framework
- Client sends the ID token to
POST /v1/auth/apple - Server validates the token by:
- Fetching Apple’s public JWKS keys from
https://appleid.apple.com/auth/keys - Caching keys for 1 hour with
sync.RWMutexfor concurrent reads - Verifying the RS256 signature against the matching key
- Validating the audience (bundle ID) and issuer claims
- Fetching Apple’s public JWKS keys from
- User resolution follows the same pattern as Google
User Resolution Pattern
Section titled “User Resolution Pattern”findOrCreateOAuthUser: 1. Match by provider + provider_id --> login 2. Match by email --> link account + login 3. No match --> create new user + loginGuest Sessions
Section titled “Guest Sessions”Guest sessions enable unauthenticated callers to join video calls without creating an account:
- Client calls
POST /v1/auth/guest-sessionwith an optional display name - Server generates a session with:
- A random hex token (via
crypto/rand) - 24-hour expiry
guestrole in JWT claims
- A random hex token (via
- The guest token can access:
- Call join endpoints (
POST /v1/calls/join/:short_code) - Call end endpoints (
POST /v1/calls/:id/end) - Call status endpoints (
GET /v1/calls/:id)
- Call join endpoints (
- Guest sessions cannot access profile, chat, follow, or admin endpoints
Role Progression
Section titled “Role Progression”Users follow a strict role progression:
guest --> user --> verified_user --> host --> admin| Transition | Trigger |
|---|---|
| guest to user | User registers with email/password |
| user to verified_user | User verifies their email |
| verified_user to host | User completes age verification + POST /v1/users/me/become-host |
| host to admin | Manual promotion by existing admin |
Each role inherits the permissions of the previous role. The API gateway’s auth middleware checks the role claim in the JWT for endpoint access.
Password Security
Section titled “Password Security”- Hashing: bcrypt with cost factor 12
- No plaintext: Passwords are never logged, stored, or transmitted in plaintext
- Rate limiting: 5 login attempts per 15 minutes per IP
- No enumeration: Password reset always returns the same message regardless of email existence
Middleware Chain
Section titled “Middleware Chain”The authentication middleware is applied per-route:
Request --> SecurityHeaders --> RateLimit --> AuthMiddleware --> RequireRole --> Handler| Middleware | Function |
|---|---|
SecurityHeaders | X-Content-Type-Options, X-Frame-Options |
RateLimit | Per-route rate limiting (Redis-backed) |
AuthMiddleware | Validates JWT, extracts claims, sets context |
RequireRole | Checks role claim against allowed roles |
Transactional Emails
Section titled “Transactional Emails”The auth service sends transactional emails at key moments:
| Event | Email Type |
|---|---|
| Registration | Welcome email + email verification link |
| Password reset request | Password reset link (tokenized URL) |
| Email verification | Confirmation success email |
Emails are sent fire-and-forget: failures are logged via slog but never block the authentication flow. In development mode, verification tokens are logged to the console instead of being sent via email.