Reference

TypeScript client API

Exports, options, methods, events, session shape, and adapters for the unbound-auth package.

The unbound-auth package provides a result-based TypeScript client for browser and server integrations. This page describes the current 0.2.x API behavior.

Alpha status

The client API may change during alpha. Handle documented results and errors explicitly, and verify behavior when upgrading.

Package exports

unbound-auth

Value exports:

createUnboundClient
UnboundError
APIUnboundError
AuthUnboundError

Type exports:

Scope
Session
ClientOptions
UnboundClient
StorageAdapter
StorageAdapterKey
StartSignInOptions
StartSignInResult
FinishSignInOptions
FinishSignInResult
GetSessionOptions
SetSessionOptions
PublicJWK

FunctionResult, event-map types, and individual error-code union types are not exported from the package root.

unbound-auth/adapters

import {
  browserStorageAdapter,
  cookieStorageAdapter,
} from "unbound-auth/adapters";

Only the adapter functions should be treated as named exports from this subpath. Do not rely on named imports of their internal option interfaces.

Result model

Normal authentication operations return a discriminated result:

type Result<T> =
  | { data: T; error: null }
  | {
      data: null;
      error: AuthUnboundError | APIUnboundError | UnboundError | Error;
    };

The Result type itself is not exported. Expected authentication and API failures are returned rather than thrown.

const { data, error } = await auth.getSession({ verify: true });

if (error) {
  // Branch by error class and code without logging credentials.
} else {
  // data is Session | null
}

initialize() is different: it returns Promise<void> and can reject. Adapter construction misuse can also throw synchronously, and event listeners can throw during an operation.

Client options

interface ClientOptions {
  auth_url?: string;
  redirect_uri?: string;
  redirect_to?: string;
  scopes?: Array<"openid" | "profile" | "email">;
  server?: boolean;
  auto_redirect?: boolean;
  storage?: StorageAdapter;
  fetcher?: typeof fetch;
  advanced?: {
    endpoints?: {
      authorization?: string;
      token?: string;
      userinfo?: string;
    };
  };
}
OptionDefaultNotes
auth_urlhttps://unbound.rlzy.meAuthorization-server base URL
redirect_uriBrowser: <origin>/callback; server: noneRelative values resolve only in a detected browser; server use requires an absolute HTTP(S) URL
redirect_toNonePost-finish destination; relative values resolve only in a browser
scopes['profile', 'email']Unsupported values passed at runtime are silently removed by startSignIn()
serverFalseyEnables server defaults and disables browser navigation/timers
auto_redirectFalseyEnables start navigation and construction-time callback handling when redirect_to is also set
storagebrowserStorageAdapter()Token in localStorage; state and verifier in sessionStorage
fetcherGlobal fetchCustom fetch-compatible implementation
advanced.endpoints.authorization/authorizeRelative to auth_url; absolute overrides are accepted
advanced.endpoints.token/tokenRelative to auth_url; absolute overrides are accepted
advanced.endpoints.userinfo/userinfoRelative to auth_url; absolute overrides are accepted

Configuration, clone, and initial-option merges are shallow. Supplying advanced replaces the previous advanced object, although each endpoint helper falls back to its standard path when its specific override is absent.

The npm client does not redirect automatically by default. CDN behavior is intentionally not documented here because the emitted global shape has not been verified.

Browser and server behavior

BehaviorBrowser clientserver: true client
Default callback<window.location.origin>/callbackNone; absolute callback required
Construction initializationStarts automaticallyLazy
State validation in finishSignIn()Yes when code is read from the callback URLNo; app must validate state
Default token processingDecode onlyCalls /userinfo
Expiration timerYes, when numeric exp existsNo
NavigationAvailable when configuredDisabled

A runtime is considered a browser only when window.location and window.localStorage are accessible. Restricted storage access can cause browser-like code to be treated as non-browser.

Create a client

const auth = createUnboundClient({
  redirect_uri: "/auth/callback",
  scopes: ["openid", "profile", "email"],
});

createUnboundClient() accepts no options, browser options, or server options. Conditional types require a per-call redirect_uri for server clients created without one and reject server: true with auto_redirect: true at the type level. Runtime does not reject that combination, but server mode still prevents browser navigation.

Methods

MethodSuccess dataImportant behavior
initialize(): Promise<void>NoneLoads stored state, verifier, and token; sets ready; can reject
startSignIn(opts?){ url, state, challenge, verifier }Generates and stores state and S256 PKCE; may navigate in browser auto mode
finishSignIn(opts?)SessionExchanges the code, processes the token, stores it, clears pending state/verifier, and emits auth
getSession({ verify? })`Sessionnull`
setSession({ access_token, verify? })SessionProcesses and stores a supplied token, then emits auth
logout()nullClears an in-memory session and stored token, then emits local logout
clone(opts?)A new clientShallow-copies configuration and current state; listeners are not copied
on(event, callback)voidAdds a synchronous listener
off(event, callback)voidRemoves the exact callback reference

startSignIn()

await auth.startSignIn({
  redirect_uri?: string,
  scopes?: Scope[],
  auto_redirect?: boolean,
});

The client derives client_id as origin:<redirect URI origin>, generates a 32-character state and 64-character verifier, creates an S256 challenge, and stores state/verifier before returning. Treat the verifier as temporary secret material.

Starting another flow with the same storage namespace overwrites the one stored state/verifier pair. Concurrent sign-ins are not reliably supported.

finishSignIn()

In a browser, omitting code makes the client read code and state from the current URL, compare state exactly, and use the stored verifier. Supplying code explicitly skips URL parsing and state validation, even in a browser.

Server integrations must validate and consume state outside the client, then call:

await auth.finishSignIn({ code, verifier });

Despite the exported FinishSignInResult type representing a token-response shape, the method currently returns Session on success.

State and verifier are removed only after both token exchange and token processing succeed. The client does not clean callback query parameters unless it navigates to redirect_to; manual browser integrations should replace the callback URL after success.

Verification behavior

getSession({ verify: true }) and setSession({ ..., verify: true }) call /userinfo; they do not fetch JWKS or perform local JWK verification. finishSignIn() has no verify option and uses the environment default.

Without remote checking, token processing only parses JWT shape, rejects an already expired numeric exp, and copies selected claims. It does not verify signature, issuer, audience, algorithm, kid, nonce, token type, or authorization server.

logout()

Client logout is local. It does not revoke a token, end the Unbound login session, or sign out of Google, GitHub, or Discord. It removes only the token, not pending state/verifier. If no in-memory session exists, it is a no-op and may leave a stale token in storage.

Members

MemberTypeNotes
readybooleanInitialization completed; does not mean authenticated
user`Sessionnull`
configReadonly<T>Runtime getter returns the current object, not a defensive copy

The config property has a partial-merge setter at runtime even though its declared type emphasizes a readonly getter. Nested mutation and JavaScript callers can still mutate the returned object; treat that as an implementation caveat, not a supported configuration pattern.

Events

EventPayload
readyOptional session (Session or null)
authA required session (Session)
logoutA reason: user, expired, or revoked

Listeners run synchronously and listener exceptions are not isolated. on() returns void, not an unsubscribe function. Keep the exact callback to pass to off().

Session shape

interface Session {
  user?: {
    id: string;
    name?: string;
    picture?: string;
    email?: string;
    email_verified?: boolean;
  };
  access_token: string;
  expires_at?: number;
  expires_in?: number;
}

expires_at is Unix seconds. expires_in is recalculated from it when available. A token without a numeric exp has no browser expiry timer and can remain stored indefinitely; app-side token validation should require expiration.

Storage adapters

StorageAdapter handles three exact keys:

type StorageAdapterKey = "token" | "verifier" | "state";

It requires asynchronous get, set, and remove methods and may implement batch query and mutate methods. In batch mutation, null means delete.

The browser adapter defaults to unbound_token in localStorage and unbound_verifier/unbound_state in sessionStorage. Use distinct prefixes for independent clients on the same origin.

The cookie adapter supports middleware and direct request/response modes. Its defaults are HttpOnly, Secure, SameSite=Lax, Path=/, no domain, and the unbound_ prefix. Default Secure cookies are not sent over ordinary HTTP development origins.

Keep a cookie-middleware base client uninitialized and free of request state before cloning it, and never reuse a request-scoped client across requests. See the storage guide for deployment patterns and Errors for error classes and codes.

On this page