Skip to main content
Version: Next

JavaScript/TypeScript SDK

@o2id/javascript is O2ID's client SDK for JavaScript/TypeScript: a single O2IDClient class covering both runtime profiles.

  • Browser SPA: redirect login, token storage, the flow-run client (loginWithRedirect/handleRedirectCallback/getAccessToken/ getUser/logout/flows).
  • Server-side / confidential client: client_credentials, token exchange, introspection (getClientCredentialsToken/exchangeToken/ introspect).

Construct it with whichever options your profile needs — redirectUri/ usePAR for browser login, clientSecret/clientAssertion for server-side calls — and call only the methods you need; nothing in the constructor touches window, so it also works unmodified in Node. Token storage defaults to sessionStorage in a browser and in-memory otherwise, so a server-side instance works with zero storage configuration too.

Install

npm install @o2id/javascript

Browser usage

import { O2IDClient } from "@o2id/javascript";

const client = new O2IDClient({
baseUrl: "https://idp.example.com/t/acme", // tenant-scoped base — see "Multi-tenancy" below
clientId: "spa_abc123",
});

// On your "login" button:
await client.loginWithRedirect(); // navigates away; returns only on error

// On the page redirect_uri points at:
await client.handleRedirectCallback(); // exchanges the code, strips ?code&state from the URL

// Anywhere after that:
const token = await client.getAccessToken(); // cached, or silently refreshed
const user = await client.getUser(); // GET /oauth2/userinfo
client.logout(); // clears local token state

Pushed authorization requests (RFC 9126) are used automatically when the tenant's discovery document advertises support for them. Pass usePAR: true or usePAR: false to force one way or the other.

Token storage

Defaults to sessionStorage (cleared when the tab closes). Pass a different TokenStorage if that tradeoff is wrong for your app:

import { O2IDClient, LocalStorageTokenStorage, MemoryTokenStorage } from "@o2id/javascript";

new O2IDClient({ /* ... */, storage: new LocalStorageTokenStorage() }); // survives tab close
new O2IDClient({ /* ... */, storage: new MemoryTokenStorage() }); // never touches disk

Both sessionStorage and localStorage are readable by any script on the page — an XSS bug can exfiltrate tokens from either. localStorage has the larger exposure window since it survives tab close; MemoryTokenStorage has none, at the cost of re-authenticating on every page reload.

You can also implement the TokenStorage interface yourself, for example to back it with an httpOnly-cookie-backed session on your own server instead.

Flow-run client

Every authenticator O2ID supports (TOTP today; more as they're added) rides the same {flowRun, nodeView} shape described in Flows. The client hands back the raw nodeView for your app to render based on nodeView.type, so adding a new authenticator server-side never requires an SDK update:

const { flowRun, nodeView } = await client.flows.start({ trigger: "passwordless-login" });
// nodeView: { nodeId, type, fields, status, ... } — render UI based on `type`
const next = await client.flows.submit(flowRun.id, { code: "123456" });

Node.js / server-side usage

import { O2IDClient } from "@o2id/javascript";

const client = new O2IDClient({
baseUrl: "https://idp.example.com/t/acme",
clientId: "svc_abc123",
clientSecret: process.env.O2ID_CLIENT_SECRET,
});

const token = await client.getClientCredentialsToken({ audience: "https://api.example.com" });

// Organization switch / on-behalf-of delegation:
const switched = await client.exchangeToken({ subjectToken: userAccessToken, audience: "org:child-org" });

// Verifying a bearer token presented to your own API:
const result = await client.introspect(incomingToken);
if (result.active) {
// ...
}

O2ID access tokens are opaque strings, not JWTs, so verifying a token presented to your API means calling introspect() — there is no local JWKS-based verification to do instead.

Multi-tenancy

baseUrl is the full tenant-scoped base URL (/t/{tenant}). The SDK never resolves or special-cases the tenant segment itself — pass whichever tenant's base URL you're integrating with.