OAuth Helpers
Overview
Section titled “Overview”The @rakomi/node SDK provides OAuth 2.0 helpers for implementing the Authorization Code flow with PKCE. All functions are available as both standalone imports and Rakomi class methods.
Result type
Section titled “Result type”OAuth token operations return a VerifyResult<OAuthTokenResponse> — the SDK never throws:
type VerifyResult<T> = | { ok: true; data: T } | { ok: false; error: SdkError };Always check result.ok before accessing data.
generatePKCE()
Section titled “generatePKCE()”Generate a PKCE code verifier and challenge pair.
Signature
Section titled “Signature”function generatePKCE(): PkceChallengeReturn type
Section titled “Return type”interface PkceChallenge { codeVerifier: string; // 43-char base64url (32 random bytes) codeChallenge: string; // SHA-256 of verifier, base64url codeChallengeMethod: 'S256';}Example
Section titled “Example”import { generatePKCE } from '@rakomi/node';
const pkce = generatePKCE();// Store pkce.codeVerifier in a server-side cookie for the callback// Send pkce.codeChallenge in the authorize URLgenerateState()
Section titled “generateState()”Generate a random state parameter for CSRF protection.
Signature
Section titled “Signature”function generateState(): stringReturns 32 random bytes, hex-encoded (64 characters).
Example
Section titled “Example”import { generateState } from '@rakomi/node';
const state = generateState();// Store in a server-side cookie, compare on callbackbuildAuthorizeUrl()
Section titled “buildAuthorizeUrl()”Build a full /oauth/authorize URL with all required parameters.
Signature
Section titled “Signature”function buildAuthorizeUrl(options: AuthorizeUrlOptions): stringParameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
clientId | string | Yes | OAuth client ID |
redirectUri | string | Yes | Callback URL |
codeChallenge | string | Yes | PKCE code challenge |
state | string | Yes | CSRF state parameter |
scope | string | string[] | No | Scopes (default: 'openid profile email') |
baseUrl | string | No | Auth server URL (default: 'https://api.rakomi.com') |
Example
Section titled “Example”import { buildAuthorizeUrl, generatePKCE, generateState } from '@rakomi/node';
const pkce = generatePKCE();const state = generateState();
const url = buildAuthorizeUrl({ clientId: 'your_client_id', redirectUri: 'https://app.example.com/callback', codeChallenge: pkce.codeChallenge, state,});// Redirect user to `url`exchangeCode()
Section titled “exchangeCode()”Exchange an authorization code for tokens.
Signature
Section titled “Signature”async function exchangeCode( options: OAuthExchangeOptions,): Promise<VerifyResult<OAuthTokenResponse>>Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Authorization code from callback |
codeVerifier | string | Yes | PKCE verifier stored during authorize |
redirectUri | string | Yes | Must match the authorize request |
clientId | string | Yes | OAuth client ID |
clientSecret | string | Yes | OAuth client secret |
baseUrl | string | No | API URL (default: 'https://api.rakomi.com') |
Return type
Section titled “Return type”interface OAuthTokenResponse { access_token: string; token_type: string; // "Bearer" expires_in: number; // seconds (default: 900) refresh_token?: string; scope?: string;}Error codes
Section titled “Error codes”| Code | Cause | Action |
|---|---|---|
oauth/invalid_grant | Code expired, already used, or bad verifier | Request new authorization |
oauth/invalid_client | Bad client_id or client_secret | Check credentials |
oauth/invalid_request | Missing or malformed parameters | Check request format |
oauth/network_error | Network failure | Retry with backoff |
oauth/missing_client_id | clientId not provided | Add to config or options |
oauth/missing_client_secret | clientSecret not provided | Add to config or options |
Example
Section titled “Example”import { exchangeCode } from '@rakomi/node';
const result = await exchangeCode({ code: callbackCode, codeVerifier: storedVerifier, redirectUri: 'https://app.example.com/callback', clientId: 'your_client_id', clientSecret: 'your_client_secret',});
if (!result.ok) { switch (result.error.code) { case 'oauth/invalid_grant': // Code expired or already used — redirect to login break; case 'oauth/invalid_client': // Bad credentials — check config break; case 'oauth/network_error': // Transient — retry break; } return;}
const { access_token, refresh_token } = result.data;refreshToken()
Section titled “refreshToken()”Refresh an OAuth access token. Automatically serializes concurrent calls with the same refresh token to prevent accidental session revocation.
Signature
Section titled “Signature”async function refreshToken( options: OAuthRefreshOptions,): Promise<VerifyResult<OAuthTokenResponse>>Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
refreshToken | string | Yes | Refresh token from previous exchange |
clientId | string | Yes | OAuth client ID |
clientSecret | string | Yes | OAuth client secret |
baseUrl | string | No | API URL (default: 'https://api.rakomi.com') |
Concurrent refresh serialization
Section titled “Concurrent refresh serialization”Rakomi implements refresh token rotation with reuse detection. If a refresh token is used twice (e.g., from concurrent requests), all user sessions are revoked (nuclear revocation). The SDK prevents this by deduplicating concurrent calls — only one HTTP request is made, and both callers receive the same result.
Example
Section titled “Example”import { refreshToken } from '@rakomi/node';
const result = await refreshToken({ refreshToken: storedRefreshToken, clientId: 'your_client_id', clientSecret: 'your_client_secret',});
if (result.ok) { // Store new tokens — refresh token may have rotated saveTokens(result.data.access_token, result.data.refresh_token);}Rakomi class methods
Section titled “Rakomi class methods”All OAuth functions are also available as class methods with auto-filled config:
import { Rakomi } from '@rakomi/node';
const ca = new RakomiClient({ apiKey: 'akm_live_xxx', clientId: 'your_client_id', clientSecret: 'your_client_secret',});
// PKCE and state — no config neededconst pkce = ca.generatePKCE();const state = ca.generateState();
// URL — auto-fills clientId and baseUrl from configconst url = ca.buildAuthorizeUrl({ redirectUri: 'https://app.example.com/callback', codeChallenge: pkce.codeChallenge, state,});
// Exchange — auto-fills clientId, clientSecret, baseUrlconst result = await ca.exchangeCode({ code, codeVerifier: pkce.codeVerifier, redirectUri: 'https://app.example.com/callback',});