Skip to content

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.

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

  • 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
{
"sub": "01JN2X4K8M3F7QPZRVWT6YBHCE",
"email": "jane@example.com",
"role": "host",
"iat": 1710000000,
"exp": 1710000900,
"iss": "api.vidivo.app"
}
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 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 |
  1. Client obtains a Google ID token using the Google Sign-In SDK
  2. Client sends the ID token to POST /v1/auth/google
  3. Server validates the token using google.golang.org/api/idtoken.Validate() with the configured client ID
  4. Server extracts the email and Google user ID from the token claims
  5. 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
  1. Client obtains an Apple identity token using Apple’s AuthenticationServices framework
  2. Client sends the ID token to POST /v1/auth/apple
  3. 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.RWMutex for concurrent reads
    • Verifying the RS256 signature against the matching key
    • Validating the audience (bundle ID) and issuer claims
  4. User resolution follows the same pattern as Google
findOrCreateOAuthUser:
1. Match by provider + provider_id --> login
2. Match by email --> link account + login
3. No match --> create new user + login

Guest sessions enable unauthenticated callers to join video calls without creating an account:

  1. Client calls POST /v1/auth/guest-session with an optional display name
  2. Server generates a session with:
    • A random hex token (via crypto/rand)
    • 24-hour expiry
    • guest role in JWT claims
  3. 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)
  4. Guest sessions cannot access profile, chat, follow, or admin endpoints

Users follow a strict role progression:

guest --> user --> verified_user --> host --> admin
TransitionTrigger
guest to userUser registers with email/password
user to verified_userUser verifies their email
verified_user to hostUser completes age verification + POST /v1/users/me/become-host
host to adminManual 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.

  • 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

The authentication middleware is applied per-route:

Request --> SecurityHeaders --> RateLimit --> AuthMiddleware --> RequireRole --> Handler
MiddlewareFunction
SecurityHeadersX-Content-Type-Options, X-Frame-Options
RateLimitPer-route rate limiting (Redis-backed)
AuthMiddlewareValidates JWT, extracts claims, sets context
RequireRoleChecks role claim against allowed roles

The auth service sends transactional emails at key moments:

EventEmail Type
RegistrationWelcome email + email verification link
Password reset requestPassword reset link (tokenized URL)
Email verificationConfirmation 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.