Arctic

OAuth 2.0

Most providers require a client ID, client secret, and redirect URI. The API is nearly identical across providers but always check each provider's guide before implementing.

import { GitHub } from "arctic";

const github = new GitHub(clientId, clientSecret, redirectURI);

Create authorization URL

Generate state using generateState() and store it as a cookie. Use it to create an authorization URL with createAuthorizationURL() and redirect the user to it.

import { generateState } from "arctic";

const state = generateState();

const scopes = ["user:email", "repo"];
const url = github.createAuthorizationURL(state, scopes);

// store state as cookie
setCookie("state", state, {
	secure: true, // set to false in localhost
	path: "/",
	httpOnly: true,
	maxAge: 60 * 10 // 10 min
});

return redirect(url);

Validate authorization code

Compare the state, and use validateAuthorizationCode() to validate the authorization code. This returns an OAuth2Tokens, or throw one of OAuth2RequestError, ArcticFetchError, or a standard Error (parse errors).

import { OAuth2RequestError, ArcticFetchError } from "arctic";

const code = request.url.searchParams.get("code");
const state = request.url.searchParams.get("state");

const storedState = getCookie("state");

if (code === null || storedState === null || state !== storedState) {
	// 400
	throw new Error("Invalid request");
}

try {
	const tokens = await github.validateAuthorizationCode(code);
	const accessToken = tokens.accessToken();
} catch (e) {
	if (e instanceof OAuth2RequestError) {
		// Invalid authorization code, credentials, or redirect URI
		const code = e.code;
		// ...
	}
	if (e instanceof ArcticFetchError) {
		// Failed to call `fetch()`
		const cause = e.cause;
		// ...
	}
	// Parse error
}

Calling OAuth2Tokens.accessToken() for example parses the response and returns the access_token field. If it doesn't exist, it will throw a parse Error. See each provider's guides for the actual return values.

const accessToken = tokens.accessToken();
const accessTokenExpiresInSeconds = tokens.accessTokenExpiresInSeconds();
const accessTokenExpiresAt = tokens.accessTokenExpiresAt();
const refreshToken = tokens.refreshToken();
const idToken = tokens.idToken();

Arctic provides decodeIdToken() for decoding the token's payload.

const claims = decodeIdToken(idToken);