Overview
PrefID uses OAuth 2.0 with PKCE (Proof Key for Code Exchange) for secure authentication. This ensures that user preferences are only accessible with explicit user consent.
OAuth Flow
Step 1: Register Your Application
Create an OAuth application in the Developer Portal :
Go to Developer Portal → Create OAuth Application
Enter your app details:
App Name : Display name shown to users
Redirect URI : Where to redirect after authorization
Scopes : Permissions your app needs
Save your client_id and client_secret
Your client_secret is only shown once. Store it securely!
Step 2: Generate PKCE Codes
import crypto from 'crypto' ;
function generateCodeVerifier () {
return crypto . randomBytes ( 32 ). toString ( 'base64url' );
}
function generateCodeChallenge ( verifier : string ) {
return crypto
. createHash ( 'sha256' )
. update ( verifier )
. digest ( 'base64url' );
}
const codeVerifier = generateCodeVerifier ();
const codeChallenge = generateCodeChallenge ( codeVerifier );
// Store codeVerifier in session for later use
Step 3: Redirect to Authorization
Build the authorization URL and redirect the user:
const authUrl = new URL ( 'https://pref-id.vercel.app/oauth/authorize' );
authUrl . searchParams . set ( 'client_id' , process . env . PREFID_CLIENT_ID );
authUrl . searchParams . set ( 'redirect_uri' , 'https://yourapp.com/callback' );
authUrl . searchParams . set ( 'response_type' , 'code' );
authUrl . searchParams . set ( 'scope' , 'preferences:read music_preferences' );
authUrl . searchParams . set ( 'state' , 'random-state-for-csrf' );
authUrl . searchParams . set ( 'code_challenge' , codeChallenge );
authUrl . searchParams . set ( 'code_challenge_method' , 'S256' );
// Redirect user
res . redirect ( authUrl . toString ());
Authorization Parameters
Parameter Required Description client_idYes Your application’s client ID redirect_uriYes Where to redirect after auth response_typeYes Must be code scopeYes Space-separated list of scopes stateRecommended CSRF protection token code_challengeYes PKCE code challenge code_challenge_methodYes Must be S256
Step 4: Handle Callback
After user authorizes, they’re redirected to your callback URL:
// GET /callback?code=xxx&state=xxx
app . get ( '/callback' , async ( req , res ) => {
const { code , state } = req . query ;
// Verify state matches what you sent
if ( state !== storedState ) {
return res . status ( 400 ). send ( 'Invalid state' );
}
// Exchange code for tokens
const tokens = await exchangeCode ( code );
// Store tokens securely
req . session . tokens = tokens ;
res . redirect ( '/dashboard' );
});
Step 5: Exchange Code for Tokens
async function exchangeCode ( code : string ) {
const response = await fetch ( 'https://api.prefid.dev/oauth/token' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/x-www-form-urlencoded' ,
},
body: new URLSearchParams ({
grant_type: 'authorization_code' ,
client_id: process . env . PREFID_CLIENT_ID ,
client_secret: process . env . PREFID_CLIENT_SECRET ,
code: code ,
redirect_uri: 'https://yourapp.com/callback' ,
code_verifier: storedCodeVerifier ,
}),
});
return response . json ();
}
Token Response
{
"access_token" : "prefid_at_xxxxx" ,
"refresh_token" : "prefid_rt_xxxxx" ,
"token_type" : "Bearer" ,
"expires_in" : 3600 ,
"scope" : "preferences:read music_preferences"
}
Step 6: Use Access Token
const response = await fetch ( 'https://api.prefid.dev/v1/preferences/music_preferences' , {
headers: {
'Authorization' : `Bearer ${ accessToken } ` ,
},
});
const preferences = await response . json ();
Refreshing Tokens
Access tokens expire after 1 hour. Use the refresh token to get new ones:
async function refreshAccessToken ( refreshToken : string ) {
const response = await fetch ( 'https://api.prefid.dev/oauth/token' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/x-www-form-urlencoded' ,
},
body: new URLSearchParams ({
grant_type: 'refresh_token' ,
client_id: process . env . PREFID_CLIENT_ID ,
client_secret: process . env . PREFID_CLIENT_SECRET ,
refresh_token: refreshToken ,
}),
});
return response . json ();
}
Available Scopes
Scope Description preferences:readRead all user preferences preferences:writeWrite/update preferences music_preferencesAccess music domain only food_profileAccess food domain only travel_profileAccess travel domain only coding_profileAccess coding domain only agent_hintsAccess agent hints profile:readRead user profile info
Security Best Practices
Always Use PKCE PKCE prevents authorization code interception attacks
Validate State Always verify the state parameter to prevent CSRF
Secure Token Storage Store tokens server-side or in encrypted storage
Request Minimal Scopes Only request the scopes you actually need