Optare v1.0 is now available. Get started →
Architecture
Auth Flows

Authentication Flows

Optare supports multiple OAuth 2.0 flows. Choose the right one for your application type.

Flow Selection Guide

Application TypeRecommended Flow
SPA (React, Vue)Authorization Code + PKCE
Server-Side Web AppAuthorization Code
Mobile AppAuthorization Code + PKCE
Backend Service (M2M)Client Credentials
Device (TV, CLI)Device Authorization

Authorization Code Flow (with PKCE)

Best for: SPAs, Mobile Apps, any public client

The most secure flow for user-facing applications.

┌──────────┐     ┌───────────┐     ┌──────────┐
│  Browser │     │  Optare   │     │ Your API │
└────┬─────┘     └─────┬─────┘     └────┬─────┘
     │                 │                │
     │ 1. Generate PKCE verifier/challenge
     │                 │                │
     │ 2. Redirect to Optare ──────────►│
     │                 │                │
     │◄──── 3. User logs in ────────────│
     │                 │                │
     │ 4. Redirect with code ◄──────────│
     │                 │                │
     │ 5. Exchange code + verifier ────►│
     │                 │                │
     │◄──── 6. Access + Refresh tokens ─│
     │                 │                │
     │ 7. Call API with token ─────────────────►│

Implementation

// 1. Generate PKCE
const verifier = generateCodeVerifier();
const challenge = await generateCodeChallenge(verifier);
 
// 2. Redirect to authorize
const params = new URLSearchParams({
  client_id: CLIENT_ID,
  redirect_uri: REDIRECT_URI,
  response_type: 'code',
  scope: 'openid profile email',
  code_challenge: challenge,
  code_challenge_method: 'S256',
});
window.location.href = `${OPTARE_DOMAIN}/oauth/authorize?${params}`;
 
// 4. Exchange code for tokens (in callback)
const tokens = await fetch('/oauth/token', {
  method: 'POST',
  body: JSON.stringify({
    grant_type: 'authorization_code',
    code: authorizationCode,
    code_verifier: verifier,
    client_id: CLIENT_ID,
    redirect_uri: REDIRECT_URI,
  }),
});

Authorization Code Flow (Confidential Client)

Best for: Server-side web apps with a backend

┌──────────┐     ┌──────────┐     ┌───────────┐
│  Browser │     │  Server  │     │  Optare   │
└────┬─────┘     └────┬─────┘     └─────┬─────┘
     │                │                 │
     │ 1. Click login │                 │
     │───────────────►│                 │
     │                │ 2. Redirect ────►
     │◄────────────────────────────────────────
     │ 3. User logs in at Optare        │
     │─────────────────────────────────►│
     │◄──── 4. Redirect with code ──────│
     │───────────────►│                 │
     │                │ 5. Exchange code│
     │                │  + client_secret│
     │                │────────────────►│
     │                │◄─── 6. Tokens ──│
     │◄─ 7. Set cookie│                 │

Implementation

// Server-side token exchange
const tokens = await fetch(`${OPTARE_DOMAIN}/oauth/token`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    grant_type: 'authorization_code',
    code: code,
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET, // Server-side only!
    redirect_uri: REDIRECT_URI,
  }),
});

Client Credentials Flow

Best for: Machine-to-machine (M2M), backend services, cron jobs

No user involved - the application authenticates itself.

┌──────────┐     ┌───────────┐
│  Service │     │  Optare   │
└────┬─────┘     └─────┬─────┘
     │                 │
     │ 1. Request token│
     │  client_id +    │
     │  client_secret  │
     │────────────────►│
     │                 │
     │◄─ 2. Access token
     │                 │
     │ 3. Call API ────────────────►

Implementation

const response = await fetch(`${OPTARE_DOMAIN}/oauth/token`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    grant_type: 'client_credentials',
    client_id: process.env.SERVICE_CLIENT_ID,
    client_secret: process.env.SERVICE_CLIENT_SECRET,
    audience: 'https://api.myapp.com',
  }),
});
 
const { access_token } = await response.json();

Refresh Token Flow

Best for: Maintaining sessions without re-authentication

┌──────────┐     ┌───────────┐
│   App    │     │  Optare   │
└────┬─────┘     └─────┬─────┘
     │                 │
     │ Access token    │
     │ expired         │
     │                 │
     │ 1. Send refresh │
     │    token        │
     │────────────────►│
     │                 │
     │◄─ 2. New access │
     │    + refresh    │
     │    tokens       │

Implementation

const response = await fetch(`${OPTARE_DOMAIN}/oauth/token`, {
  method: 'POST',
  body: JSON.stringify({
    grant_type: 'refresh_token',
    refresh_token: storedRefreshToken,
    client_id: CLIENT_ID,
  }),
});

Device Authorization Flow

Best for: TVs, CLI tools, devices without browsers

┌──────────┐     ┌───────────┐     ┌──────────┐
│  Device  │     │  Optare   │     │  Phone   │
└────┬─────┘     └─────┬─────┘     └────┬─────┘
     │                 │                │
     │ 1. Get code ───►│                │
     │◄─ device_code,  │                │
     │   user_code     │                │
     │                 │                │
     │ 2. Show code    │                │
     │   "Enter ABC123"│                │
     │                 │                │
     │                 │◄─ 3. User enters
     │                 │      code      │
     │                 │                │
     │ 4. Poll ───────►│                │
     │◄─ access_token ─│                │

Security Best Practices

PracticeDescription
Always use PKCEEven for confidential clients
Short token lifetimes15 min - 1 hour for access tokens
Rotate refresh tokensIssue new refresh token on use
Validate tokens server-sideCheck iss, aud, exp claims
Use httpOnly cookiesFor refresh tokens in web apps

Next Steps