Skip to main content

OAuth Flow

Complete step-by-step breakdown of the SVA OAuth 2.0 authorization code flow.

Flow Overview

┌─────────┐         ┌──────────────┐         ┌─────────────┐         ┌──────────────┐
│ App │────────▶│ SVA OAuth │────────▶│ SVA Client │────────▶│ SVA Server │
│ │ │ Provider │ │ (Consent) │ │ (Attest) │
└─────────┘ └──────────────┘ └─────────────┘ └──────────────┘

Step-by-Step Flow

Step 1: Authorization Request

Location: Your App → SVA OAuth Provider

Action: User clicks "Sign In with SVA" or accesses a protected view

# Your app generates authorization URL
auth_url, code_verifier = client.get_authorization_url()
# Redirects to: https://auth.getsva.com/oauth/authorize?client_id=...&redirect_uri=...&scope=...&code_challenge=...&state=...

What Happens:

  1. App generates PKCE parameters (code_verifier, code_challenge)
  2. App generates state parameter for CSRF protection
  3. App redirects user to SVA OAuth authorization endpoint
  4. SVA OAuth validates client_id and redirect_uri
  5. SVA OAuth creates OAuthAuthorizationRequest record
  6. SVA OAuth redirects to consent screen

Key Parameters:

  • client_id: Your OAuth app client ID
  • redirect_uri: Your callback URL
  • response_type: code
  • scope: Requested scopes (e.g., openid email profile)
  • code_challenge: PKCE code challenge
  • code_challenge_method: S256
  • state: CSRF protection token

Location: SVA Client (React App)

Action: User reviews and approves requested scopes

What Happens:

  1. Consent page loads with auth_request_id from URL
  2. Frontend fetches auth request details from SVA Server
  3. SVA Server proxies request to SVA OAuth with service token
  4. Consent page displays:
    • Application name, logo, description
    • Requested scopes with descriptions
    • User can toggle individual scopes
  5. User unlocks vault if needed (master key or passkey)
  6. User clicks "Approve and Continue"

Key Components:

  • /consent?auth_request_id={uuid} - Consent page URL
  • GET /api/internal/oauth/requests/{id}/ - Fetch request details
  • User's encrypted vault data (decrypted client-side)

Step 3: Data Attestation

Location: SVA Client → SVA Server

Action: SVA Core issues signed data token

What Happens:

  1. Frontend decrypts user data from vault
  2. Frontend builds claims object from approved scopes:
    {
    "email": "[email protected]",
    "name": "John Doe",
    "phone": "+1234567890"
    }
  3. Frontend calls SVA Server attestation endpoint:
    POST /api/internal/attest-data/
    {
    "auth_request_id": "...",
    "user_id": "...",
    "audience": "client_id",
    "claims": {...}
    }
  4. SVA Server validates:
    • User session token
    • Auth request exists and is pending
    • Claims match requested scopes
  5. SVA Server generates signed data token (JWT):
    {
    "sub": "user_id",
    "aud": "client_id",
    "auth_request_id": "...",
    "claims": {...},
    "exp": 1234567890,
    "iat": 1234567890
    }
  6. SVA Server returns data token

Key Endpoints:

  • POST /api/internal/attest-data/ - Data attestation endpoint
  • Data token signed with DATA_TOKEN_SECRET
  • Token expires in 5 minutes (default)

Location: SVA Client → SVA Server → SVA OAuth

Action: Notify OAuth provider that consent is complete

What Happens:

  1. Frontend calls SVA Server:
    POST /api/internal/oauth/requests/{auth_request_id}/complete/
    {
    "approved_scopes": ["email", "profile"],
    "data_token": "..."
    }
  2. SVA Server proxies to SVA OAuth:
    POST /api/auth/internal/consent-complete/
    Headers: {
    "X-Service-Token": "shared-secret"
    }
    Body: {
    "auth_request_id": "...",
    "user_id": "...",
    "approved_scopes": [...],
    "data_token": "..."
    }
  3. SVA OAuth validates:
    • Service token
    • Data token signature
    • Data token audience matches client_id
    • Data token subject matches user_id
    • Auth request is still pending
  4. SVA OAuth creates OAuthAuthorizationCode:
    • Generates unique authorization code
    • Links to auth request
    • Stores approved scopes
    • Stores data token
    • Expires in 10 minutes
  5. SVA OAuth updates auth request status to approved
  6. SVA OAuth returns redirect URL with authorization code

Key Components:

  • Authorization code (one-time use, expires in 10 minutes)
  • Data token stored with authorization code
  • Redirect URL: {redirect_uri}?code={code}&state={state}

Step 5: Token Exchange

Location: Your App → SVA OAuth Provider

Action: Exchange authorization code for tokens

What Happens:

  1. User's browser redirects to your callback URL:
    https://yourapp.com/oauth/callback/?code={code}&state={state}
  2. Your app validates state parameter (CSRF protection)
  3. Your app exchanges code for tokens:
    POST https://auth.getsva.com/oauth/token/
    {
    "grant_type": "authorization_code",
    "code": "...",
    "redirect_uri": "...",
    "client_id": "...",
    "client_secret": "...",
    "code_verifier": "..."
    }
  4. SVA OAuth validates:
    • Client credentials
    • Authorization code exists and not used
    • Authorization code not expired
    • Redirect URI matches
    • PKCE code verifier (if used)
  5. SVA OAuth creates tokens:
    • Access token (expires in 1 hour)
    • Refresh token (expires in 30 days)
    • Includes data token from authorization code
  6. SVA OAuth returns tokens:
    {
    "access_token": "...",
    "token_type": "Bearer",
    "expires_in": 3600,
    "refresh_token": "...",
    "scope": "openid email profile",
    "data_token": "..."
    }
  7. Your app stores tokens in session

Key Components:

  • Access token for API calls
  • Refresh token for token renewal
  • Data token containing user claims (stateless!)

Step 6: Access User Data

Location: Your App

Action: Decode user data from data token

What Happens:

  1. Your app retrieves data token from session
  2. Your app decodes and verifies data token:
    claims = client.get_blocks_data(data_token)
    # Or using utility:
    claims = get_sva_claims(request)
  3. Your app accesses user data:
    email = claims.get('email')
    name = claims.get('name')
    phone = claims.get('phone')

Key Benefits:

  • No API call to /userinfo needed
  • Data available immediately
  • Cryptographically verified
  • Stateless design

Token Lifecycle

Authorization Code

  • Lifetime: 10 minutes
  • Usage: One-time use
  • Storage: SVA OAuth database

Data Token

  • Lifetime: 5 minutes (default)
  • Usage: Included in token response
  • Storage: Your app session

Access Token

  • Lifetime: 1 hour
  • Usage: API authentication
  • Storage: Your app session

Refresh Token

  • Lifetime: 30 days
  • Usage: Token renewal
  • Storage: Your app session

Security Features

PKCE (Proof Key for Code Exchange)

  • Prevents authorization code interception
  • Code verifier generated client-side
  • Code challenge sent in authorization request
  • Code verifier verified in token exchange

State Parameter

  • CSRF protection
  • Random token generated per flow
  • Stored in session
  • Verified in callback

Data Token Verification

  • Cryptographic signature verification
  • Expiration validation
  • Audience validation (client_id)
  • Subject validation (user_id)

Error Handling

Common Errors

invalid_client

  • Invalid client_id or client_secret
  • Solution: Verify credentials

redirect_uri_mismatch

  • Redirect URI doesn't match registered URI
  • Solution: Ensure exact match (including protocol, domain, port, path)

invalid_grant

  • Authorization code invalid, expired, or already used
  • Solution: Request new authorization

invalid_token

  • Data token invalid or expired
  • Solution: Request new authorization

Next Steps