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.