AbsoluteJS
AbsoluteJS

Citra

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 GitHub
#

Why Citra?

Interchangeability

All OAuth 2.0 providers follow the same flow. Citra abstracts this into a unified interface.

Type Safety

TypeScript generics and type guards catch configuration mistakes at compile time.

Inspired by Arctic, Citra reduces boilerplate and minimizes integration errors by enforcing a uniform configuration approach.

#

Installation

BASH
bun install citra
#

Getting Started

Citra 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.

TS
1import { createOAuth2Client } from 'citra';
2
3const googleClient = await createOAuth2Client('google', {
4  clientId: 'YOUR_CLIENT_ID',
5  clientSecret: 'YOUR_CLIENT_SECRET',
6  redirectUri: 'https://yourapp.com/auth/callback'
7});
#

Building the Authorization URL

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.

TS
1const currentState = generateState();
2const codeVerifier = generateCodeVerifier();
3const authUrl = await googleClient.createAuthorizationUrl({
4	codeVerifier,
5	scope: ['profile', 'openid'],
6	searchParams: [
7		['access_type', 'offline'],
8		['prompt', 'consent']
9	],
10	state: currentState
11});
12
13// Store state and PKCE verifier in HttpOnly cookies
14const headers = new Headers();
15headers.set('Location', authUrl.toString());
16headers.append(
17	'Set-Cookie',
18	`oauth_state=${currentState}; HttpOnly; Path=/; Secure; SameSite=Lax`
19);
20headers.append(
21	'Set-Cookie',
22	`pkce_code_verifier=${codeVerifier}; HttpOnly; Path=/; Secure; SameSite=Lax`
23);
#

Handling the Callback

Exchange the authorization code and PKCE verifier for an OAuth2 token response:

TS
1// Parse callback URL parameters
2const request = new Request('https://yourapp.com/auth/callback');
3const params = new URL(request.url).searchParams;
4const code = params.get('code');
5const callback_state = params.get('state');
6
7// Retrieve stored state and code verifier from cookies
8const cookieHeader = request.headers.get('cookie') ?? '';
9const cookies = cookieHeader.trim()
10  ? Object.fromEntries(
11      cookieHeader
12        .split('; ')
13        .filter(c => c.includes('='))
14        .map(c => c.split('='))
15    )
16  : {};
17
18const stored_state = cookies['oauth_state'];
19const codeVerifier = cookies['pkce_code_verifier'];
20
21// Validate required cookies are present
22if (!stored_state) {
23  throw new Error('Missing oauth_state cookie');
24}
25if (!codeVerifier) {
26  throw new Error('Missing pkce_code_verifier cookie');
27}
28
29// Validate state to prevent CSRF attacks
30if (!callback_state || callback_state !== stored_state) {
31  throw new Error('Invalid state mismatch');
32}
33
34// Validate code is present
35if (!code) {
36  throw new Error('Authorization code not found');
37}
38
39// Exchange authorization code for tokens
40const tokenResponse = await googleClient.validateAuthorizationCode({
41  code,
42  codeVerifier
43});
#

Fetching the User Profile

Exchange the access token for user information:

TS
1// Get the access token from server session
2const session = await getSession(request);
3const accessToken = session.accessToken;
4
5const profile = await googleClient.fetchUserProfile(accessToken);
6console.log(profile);
#

Token Management

If supported by the provider, you can refresh and revoke tokens:

TS
1// Get the refresh token from server session
2const session = await getSession(request);
3const refreshToken = session.refreshToken;
4
5if (refreshToken) {
6  const newTokens = await googleClient.refreshAccessToken(refreshToken);
7}
TS
1// Get the access token from server session
2const session = await getSession(request);
3const accessToken = session.accessToken;
4
5if (isRevocableProviderOption('google')) {
6  await googleClient.revokeToken(accessToken);
7}
#

Supported Providers

Citra supports 66 OAuth 2.0 providers:

42 logo42
Amazon Cognito logoAmazon Cognito
AniList logoAniList
Apple logoApple
Atlassian logoAtlassian
Auth0 logoAuth0
Authentik logoAuthentik
Autodesk logoAutodesk
Battle.net logoBattle.net
Bitbucket logoBitbucket
Box logoBox
Bungie logoBungie
Coinbase logoCoinbase
Discord logoDiscord
Donation Alerts logoDonation Alerts
Dribbble logoDribbble
Dropbox logoDropbox
Epic Games logoEpic Games
Etsy logoEtsy
Facebook logoFacebook
Figma logoFigma
Gitea logoGitea
GitHub logoGitHub
GitLab logoGitLab
Google logoGoogle
Intuit logoIntuit
Kakao logoKakao
Keycloak logoKeycloak
Kick logoKick
Lichess logoLichess
LINE logoLINE
Linear logoLinear
LinkedIn logoLinkedIn
Mastodon logoMastodon
Mercado Libre logoMercado Libre
Mercado Pago logoMercado Pago
Microsoft Entra ID logoMicrosoft Entra ID
MyAnimeList logoMyAnimeList
Naver logoNaver
Notion logoNotion
Okta logoOkta
osu! logoosu!
Patreon logoPatreon
Polar logoPolar
Polar Access Link logoPolar Access Link
Polar Team Pro logoPolar Team Pro
Reddit logoReddit
Roblox logoRoblox
Salesforce logoSalesforce
Shikimori logoShikimori
Slack logoSlack
Spotify logoSpotify
Start.gg logoStart.gg
Strava logoStrava
Synology logoSynology
TikTok logoTikTok
Tiltify logoTiltify
Tumblr logoTumblr
Twitch logoTwitch
Twitter / X logoTwitter / X
VK logoVK
Withings logoWithings
WorkOS logoWorkOS
Yahoo logoYahoo
Yandex logoYandex
Zoom logoZoom