Forge.Core Docs
Launch Dashboard

Client SDK

@casteluke/forge-client wraps the "Sign in with Forge" flow described in /docs/oauth into a small, framework-agnostic core plus a React adapter — so you don't have to hand-roll PKCE and token exchange yourself.

Workspace-only for now

This package is private: true and lives at packages/forge-client — not published to npm yet. Consume it as a workspace dependency from another package/app in this monorepo ("@casteluke/forge-client": "*").

Core (framework-agnostic)

Use these directly if you're not on React, or to build a platform adapter of your own (React Native, for instance).

TypeScript
import {
  generateState,
  generatePkcePair,
  resolveForgeEndpoints,
  buildAuthorizeUrl,
  exchangeCodeForToken,
  verifyIdToken,
  fetchUserinfo,
} from "@casteluke/forge-client";

const endpoints = resolveForgeEndpoints("https://auth.yourapp.com");

// 1. Kick off sign-in — redirect the user here.
const state = generateState();
const pkce = await generatePkcePair();
// Persist state and pkce.codeVerifier somewhere that survives the redirect.
const authorizeUrl = buildAuthorizeUrl({
  authorizeEndpoint: endpoints.authorizeEndpoint,
  clientId: "your-client-id",
  redirectUri: "https://yourapp.com/callback",
  state,
  codeChallenge: pkce.codeChallenge,
});

// 2. On the callback (?code=...&state=...):
const tokens = await exchangeCodeForToken({
  tokenEndpoint: endpoints.tokenEndpoint,
  clientId: "your-client-id",
  code: "the-code-from-the-query-string",
  redirectUri: "https://yourapp.com/callback",
  codeVerifier: pkce.codeVerifier,
});

const claims = await verifyIdToken({
  idToken: tokens.id_token,
  jwksUri: endpoints.jwksUri,
  issuer: "https://auth.yourapp.com",
  audience: "your-client-id",
});

const userinfo = await fetchUserinfo(endpoints.userinfoEndpoint, tokens.access_token);

React adapter

Owns the whole browser-redirect flow for a public client — no client secret ever touches the browser.

TypeScript / TSX
import { ForgeAuthProvider, useForgeAuth } from "@casteluke/forge-client/react";

function App() {
  return (
    <ForgeAuthProvider
      forgeBaseUrl="https://auth.yourapp.com"
      clientId="your-client-id"
      redirectUri="https://yourapp.com/callback"
    >
      <Page />
    </ForgeAuthProvider>
  );
}

function Page() {
  const { user, accessToken, loading, error, signIn, signOut } = useForgeAuth();

  if (loading) return <p>Loading...</p>;
  if (!user) return <button onClick={signIn}>Sign in with Forge</button>;

  return (
    <div>
      <p>Signed in as {user.email}</p>
      <button onClick={signOut}>Sign out</button>
    </div>
  );
}

Mount <ForgeAuthProvider> on the same route as redirectUri — it detects the ?code&state landing on that page, completes the exchange, and strips those query params once done.

signOut() is local-only

It clears in-memory state but doesn't call /oauth2/revoke — wire that call in yourself if you need the access token actually invalidated server-side, not just forgotten by your app.

React Native

Not implemented yet — deferred so the core above could ship first and be shaped by real usage before committing to an RN-specific API. The core works as-is in React Native with a getRandomValues polyfill (react-native-get-random-values) and, depending on your RN/Hermes version, a btoa polyfill. The React adapter's window.location/sessionStorage calls won't work as-is — React Native needs its own adapter using deep links (Linking) instead.