Skip to main content

Authentication Flow

ConsentKeys implements the OAuth 2.0 Authorization Code flow with passwordless magic link authentication. This page explains how the complete flow works from start to finish.

Overview

The authentication flow consists of several steps that ensure secure, privacy-focused user authentication:

Detailed Breakdown

Step 1: Initiate Authorization

Your application redirects the user to the ConsentKeys authorization endpoint:

GET /auth?
response_type=code&
client_id=ck_your_client_id&
redirect_uri=https://yourapp.com/callback&
scope=openid profile email&
state=random_csrf_token&
nonce=random_replay_token

Required Parameters:

  • response_type: Must be code for authorization code flow
  • client_id: Your application's client ID
  • redirect_uri: Where to send the user after authentication (must match registered URI)
  • scope: Requested permissions (must include openid)

Recommended Parameters:

  • state: Random string for CSRF protection (verify this matches on callback)
  • nonce: Random string to prevent token replay attacks
  • code_challenge: PKCE code challenge for public clients
  • code_challenge_method: Usually S256 (SHA-256)

Optional Parameters:

  • prompt: Controls authentication behavior
    • login: Force user to re-authenticate even if they have an active session
    • consent: Force consent screen to be shown (note: consent behavior depends on frontend implementation)

Step 2-3: Email Collection

ConsentKeys displays an email input screen. If the user has an existing session, they'll see an account chooser instead.

New User Experience:

┌────────────────────────────────────────┐
│ │
│ Sign in to YourApp │
│ │
│ ┌────────────────────────────────┐ │
│ │ Enter your email │ │
│ └────────────────────────────────┘ │
│ │
│ [Continue] │
│ │
└────────────────────────────────────────┘

Returning User Experience:

┌────────────────────────────────────────┐
│ │
│ Choose an account for YourApp │
│ │
│ ┌────────────────────────────────┐ │
│ │ 👤 john@example.com │ │
│ └────────────────────────────────┘ │
│ ┌────────────────────────────────┐ │
│ │ 👤 jane@work.com │ │
│ └────────────────────────────────┘ │
│ │
│ + Use a different account │
│ │
└────────────────────────────────────────┘
POST /request-magic-link
Content-Type: application/json

{
"email": "user@example.com",
"client_id": "ck_your_client_id",
"redirect_uri": "https://yourapp.com/callback",
"scope": "openid profile email",
"state": "...",
"nonce": "..."
}

ConsentKeys sends an email with a secure, time-limited link:

Subject: Sign in to YourApp

Click here to sign in:
https://pseudoidc.consentkeys.com/verify-email?token=...

This link expires in 15 minutes.

When the user clicks the link, ConsentKeys:

  1. Validates the token
  2. Creates or retrieves the user account
  3. Establishes a browser session

The user sees what data your app is requesting:

┌────────────────────────────────────────┐
│ │
│ YourApp wants to access: │
│ │
│ ✓ Your email address │
│ ✓ Your profile information │
│ │
│ [Allow] [Deny] │
│ │
└────────────────────────────────────────┘

If the user has already granted consent to your app, this screen is skipped.

Step 10: Authorization Code Redirect

After consent, ConsentKeys redirects back to your app:

HTTP/1.1 302 Found
Location: https://yourapp.com/callback?
code=auth_code_abc123&
state=random_csrf_token

Your app must:

  1. Verify the state parameter matches what you sent
  2. Extract the authorization code

Step 11-12: Token Exchange

Your backend server exchanges the code for tokens:

POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=auth_code_abc123&
redirect_uri=https://yourapp.com/callback&
client_id=ck_your_client_id&
client_secret=your_client_secret

Response:

{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"scope": "openid profile email"
}
Keep Client Secret Secure

The token exchange must happen on your backend server. Never expose your client secret in frontend code!

Step 13-14: User Information

Use the access token to get user details:

GET /userinfo
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...

Response:

{
"sub": "user_unique_id",
"email": "user@example.com",
"email_verified": true,
"name": "John Doe",
"preferred_username": "johnd",
"picture": "https://..."
}

Security Considerations

CSRF Protection (State Parameter)

Always include and verify the state parameter:

// Generate state before redirect
const state = generateRandomString(32);
sessionStorage.setItem('oauth_state', state);

// Verify on callback
const returnedState = new URLSearchParams(window.location.search).get('state');
if (returnedState !== sessionStorage.getItem('oauth_state')) {
throw new Error('Invalid state - possible CSRF attack');
}

PKCE for Public Clients

For single-page apps and mobile apps, use PKCE:

// Generate code verifier
const codeVerifier = generateRandomString(128);
sessionStorage.setItem('code_verifier', codeVerifier);

// Generate code challenge
const challenge = await sha256(codeVerifier);
const codeChallenge = base64UrlEncode(challenge);

// Include in authorization request
const authUrl = `https://pseudoidc.consentkeys.com/auth?
...&code_challenge=${codeChallenge}&code_challenge_method=S256`;

// Include in token exchange
const tokenResponse = await fetch('/token', {
method: 'POST',
body: new URLSearchParams({
grant_type: 'authorization_code',
code: authCode,
code_verifier: sessionStorage.getItem('code_verifier'),
...
})
});

Token Storage

Best Practices:

  • Access tokens: Store in memory (React state, Vue store)
  • Refresh tokens: Store in httpOnly cookies (backend-managed)
  • Never: Store tokens in localStorage (XSS vulnerable)

Error Handling

Common OAuth errors and how to handle them:

ErrorCauseSolution
invalid_requestMissing required parameterCheck your authorization URL
access_deniedUser denied consentShow appropriate message
invalid_grantExpired/invalid auth codeRestart the flow
invalid_clientWrong client credentialsVerify your client ID/secret
unsupported_response_typeWrong response_typeUse code

See the Error Codes reference for a complete list.

Next Steps