Table of Contents
Introduction
A curated collection of OAuth 2.0 provider configurations, each bundled with the correct endpoints and request details. Ready-to-use foundation for secure authentication in TypeScript applications.
View on GitHubInspired by Arctic, Citra reduces boilerplate and minimizes integration errors by enforcing a uniform configuration approach.
bun install citraCitra uses strong TypeScript typing to help you build OAuth clients safely. Each provider includes its own typed configuration schema, ensuring you can't pass unsupported parameters or omit required ones.
import { createOAuth2Client } from 'citra';
const googleClient = await createOAuth2Client('google', {
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
redirectUri: 'https://yourapp.com/auth/callback'
});Generate a fully customized authorization URL for redirecting users to the provider's login page. Every option is strongly typed and context-aware, with full control over PKCE, scopes, and provider-specific parameters.
const currentState = generateState();
const codeVerifier = generateCodeVerifier();
const authUrl = await googleClient.createAuthorizationUrl({
codeVerifier,
scope: ['profile', 'openid'],
searchParams: [
['access_type', 'offline'],
['prompt', 'consent']
],
state: currentState
});
// Store state and PKCE verifier in HttpOnly cookies
const headers = new Headers();
headers.set('Location', authUrl.toString());
headers.append(
'Set-Cookie',
`oauth_state=${currentState}; HttpOnly; Path=/; Secure; SameSite=Lax`
);
headers.append(
'Set-Cookie',
`pkce_code_verifier=${codeVerifier}; HttpOnly; Path=/; Secure; SameSite=Lax`
);Exchange the authorization code and PKCE verifier for an OAuth2 token response:
// Parse callback URL parameters
const request = new Request('https://yourapp.com/auth/callback');
const params = new URL(request.url).searchParams;
const code = params.get('code');
const callback_state = params.get('state');
// Retrieve stored state and code verifier from cookies
const cookieHeader = request.headers.get('cookie') ?? '';
const cookies = cookieHeader.trim()
? Object.fromEntries(
cookieHeader
.split('; ')
.filter(c => c.includes('='))
.map(c => c.split('='))
)
: {};
const stored_state = cookies['oauth_state'];
const codeVerifier = cookies['pkce_code_verifier'];
// Validate required cookies are present
if (!stored_state) {
throw new Error('Missing oauth_state cookie');
}
if (!codeVerifier) {
throw new Error('Missing pkce_code_verifier cookie');
}
// Validate state to prevent CSRF attacks
if (!callback_state || callback_state !== stored_state) {
throw new Error('Invalid state mismatch');
}
// Validate code is present
if (!code) {
throw new Error('Authorization code not found');
}
// Exchange authorization code for tokens
const tokenResponse = await googleClient.validateAuthorizationCode({
code,
codeVerifier
});Exchange the access token for user information:
// Get the access token from server session
const session = await getSession(request);
const accessToken = session.accessToken;
const profile = await googleClient.fetchUserProfile(accessToken);
console.log(profile);If supported by the provider, you can refresh and revoke tokens:
// Get the refresh token from server session
const session = await getSession(request);
const refreshToken = session.refreshToken;
if (refreshToken) {
const newTokens = await googleClient.refreshAccessToken(refreshToken);
}// Get the access token from server session
const session = await getSession(request);
const accessToken = session.accessToken;
if (isRevocableProviderOption('google')) {
await googleClient.revokeToken(accessToken);
}Beyond the per-provider OAuth clients, Citra ships a discovery-driven OpenID Connect client for connecting to any compliant IdP at runtime — the foundation for enterprise SSO.
import { createOIDCClient } from 'citra';
// Discovery-driven: only the issuer + client credentials. The authorize, token,
// userinfo, and JWKS endpoints are resolved at runtime from
// {issuer}/.well-known/openid-configuration. Works with any compliant IdP.
const client = await createOIDCClient({
issuer: 'https://acme.okta.com',
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
redirectUri: 'https://yourapp.com/auth/callback'
});
const { url, state, codeVerifier, nonce } = await client.createAuthorizationUrl({
scope: ['openid', 'email', 'profile']
});
// …redirect, then on callback:
const tokens = await client.validateAuthorizationCode({ code, codeVerifier });
const profile = await client.fetchUserProfile(tokens.access_token);ID tokens are verified in-house against the issuer's JWKS (RS256 / ES256) via WebCrypto — no extra crypto dependency:
import { verifyIdToken } from 'citra';
// In-house JWKS verification (RS256 / ES256) via WebCrypto — no 'jose' dependency.
// Checks the signature against the issuer's JWKS plus iss / aud / exp (+skew) /
// nonce / sub. Network-free if you pass a cached JWKS.
const claims = await verifyIdToken(tokens.id_token, {
issuer: 'https://acme.okta.com',
audience: 'YOUR_CLIENT_ID',
nonce
});
console.log(claims.sub, claims.email);Citra supports 78 OAuth 2.0 providers:
Facebook
LinkedIn
Mercado Libre
Okta
osu!
Polar
Polar Access Link
Polar Team Pro
Slack
Slack User
Synology
Twitter / X
Withings
ZoomCurrent package surface
Outcomes
Introduction
Citra is a curated collection of OAuth 2.0 provider configurations, each bundled with the correct endpoints and request details. It provides a ready-to-use foundation for integrating secure authentication into JavaScript and TypeScript applications. See the complete Citra guide for installation, provider configuration, and integration patterns.
Interchangeability: All OAuth 2.0 providers follow the same authorization flow, and Citra abstracts this process into a unified interface (see arctic interchangeability issue).
Hardening checklist
Follow in order