SDKsTypeScript SDKOAuth 2.1
Code Exchange
Exchange authorization codes for tokens with PKCE verification using the TypeScript SDK.
Code Exchange
After the user authorizes your application, the authorization server redirects them back to your redirectUri with an authorization code. Exchange this code for access and refresh tokens.
exchangeCode()
Exchange an authorization code for a token set.
client.oauth.exchangeCode(params: ExchangeCodeParams): Promise<TokenSet>Parameters
| Name | Type | Required | Description |
|---|---|---|---|
code | string | yes | Authorization code from the callback URL |
codeVerifier | string | yes | PKCE code verifier from buildAuthorizationUrl() |
redirectUri | string | yes | Must match the redirectUri used in the authorization request |
Returns
interface TokenSet {
accessToken: string; // JWT access token
refreshToken: string; // Refresh token (if offline_access scope was requested)
idToken: string; // OIDC ID token (if openid scope was requested)
tokenType: "Bearer"; // Always "Bearer"
expiresIn: number; // Access token lifetime in seconds (typically 900)
expiresAt: string; // ISO 8601 absolute expiry time
scope: string; // Space-separated granted scopes
}Basic usage
import { AvnologyId } from "@avnology/sdk-typescript";
const client = new AvnologyId({
baseUrl: "https://api.id.avnology.com",
clientId: "app_abc123",
});
// On your callback page (e.g., /callback?code=AUTH_CODE&state=...)
const url = new URL(window.location.href);
const code = url.searchParams.get("code");
const returnedState = url.searchParams.get("state");
// Validate state to prevent CSRF
const savedState = sessionStorage.getItem("oauth_state");
if (returnedState !== savedState) {
throw new Error("State mismatch -- possible CSRF attack");
}
// Retrieve the PKCE code verifier saved during authorization
const codeVerifier = sessionStorage.getItem("pkce_code_verifier");
if (!codeVerifier) {
throw new Error("Missing PKCE code verifier");
}
// Exchange the code for tokens
const tokens = await client.oauth.exchangeCode({
code: code!,
codeVerifier,
redirectUri: "https://myapp.com/callback",
});
console.log(tokens.accessToken); // "eyJhbGciOiJSUzI1NiIs..."
console.log(tokens.refreshToken); // "rt_abc123..."
console.log(tokens.idToken); // "eyJhbGciOiJSUzI1NiIs..."
console.log(tokens.expiresIn); // 900 (15 minutes)
console.log(tokens.scope); // "openid profile email offline_access"
// Clean up session storage
sessionStorage.removeItem("pkce_code_verifier");
sessionStorage.removeItem("oauth_state");Storing tokens securely
// Browser -- use the SDK's built-in token management
client.setTokens(tokens);
// The SDK will auto-refresh when the access token expires
// Server-side -- store in encrypted session
req.session.tokens = {
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
expiresAt: tokens.expiresAt,
};Decode the ID token
The ID token is a JWT containing user identity claims. The SDK provides a helper to decode it without verification (use server-side verification for security-critical checks).
const claims = client.oauth.decodeIdToken(tokens.idToken);
console.log(claims.sub); // "usr_abc123" -- user ID
console.log(claims.email); // "[email protected]"
console.log(claims.name); // "Jane Doe"
console.log(claims.iss); // "https://id.avnology.com"
console.log(claims.aud); // "app_abc123"
console.log(claims.nonce); // Matches the nonce from authorization
console.log(claims.org_id); // "org_abc123" (if organizations scope)
console.log(claims.org_name); // "Acme Corp"Full callback handler (React Router)
// routes/callback.tsx
import { redirect } from "react-router";
import { client } from "../lib/avnology";
export async function loader({ request }: { request: Request }) {
const url = new URL(request.url);
const code = url.searchParams.get("code");
const state = url.searchParams.get("state");
const error = url.searchParams.get("error");
// Handle authorization errors
if (error) {
const description = url.searchParams.get("error_description");
console.error("OAuth error:", error, description);
return redirect(`/login?error=${encodeURIComponent(description || error)}`);
}
if (!code) {
return redirect("/login?error=missing_code");
}
// Exchange code for tokens
const codeVerifier = sessionStorage.getItem("pkce_code_verifier");
const tokens = await client.oauth.exchangeCode({
code,
codeVerifier: codeVerifier!,
redirectUri: `${url.origin}/callback`,
});
// Set tokens in the client
client.setTokens(tokens);
return redirect("/dashboard");
}Error handling
import {
InvalidGrantError,
InvalidClientError,
ExpiredCodeError,
} from "@avnology/sdk-typescript";
try {
const tokens = await client.oauth.exchangeCode({
code,
codeVerifier,
redirectUri,
});
} catch (error) {
if (error instanceof InvalidGrantError) {
// Code was already used, expired, or code_verifier doesn't match
showError("Authorization failed. Please try again.");
redirectToLogin();
} else if (error instanceof InvalidClientError) {
// Client ID or secret is wrong
console.error("OAuth client misconfigured:", error.message);
} else if (error instanceof ExpiredCodeError) {
// Authorization code expired (typically 10 minutes)
showError("Authorization expired. Please try again.");
redirectToLogin();
}
}Common errors
| Error class | HTTP status | When |
|---|---|---|
InvalidGrantError | 400 | Code already used, expired, or PKCE mismatch |
InvalidClientError | 401 | Wrong client ID or secret |
ExpiredCodeError | 400 | Authorization code expired |
InvalidRedirectUriError | 400 | Redirect URI doesn't match |
See also
- Authorization URL -- Build the authorization URL
- Tokens -- Refresh, revoke, and introspect tokens
- OAuth types -- TokenSet type definition