Base IdP

Base IdP

SDKs

TypeScript

The base-idp package — browser client, server verifier, and adapters for React, Next.js, SvelteKit, Vite, Node, NestJS, and Express.

The base-idp npm package is the largest of the SDKs because TypeScript has the most surfaces to integrate with. It ships browser code, server code, and framework adapters for the runtimes Square products run on. The CLI you have been reading about — npx base-idp create — is part of this same package.

Install

npm install base-idp

The package has no required peer dependencies. React is optional and only needed if you use the React adapter.

# With bun
bun add base-idp

# With pnpm
pnpm add base-idp

# With yarn
yarn add base-idp

Picking an entry point

The package exports several entry points. You import the one that matches your runtime and role.

Import pathWhen to use it
base-idp/browserBrowser code, SPAs, public clients.
base-idp/serverServer code that verifies tokens or runs login.
base-idp/nextNext.js route handlers.
base-idp/nodePlain Node servers (Express, Fastify, raw http).
base-idp/reactReact hooks and providers.
base-idp/sveltekitSvelteKit endpoints and hooks.
base-idp/viteVite plugins for build-time config.

Always import from the most specific entry point that fits. If you are writing Next.js route handlers, use base-idp/next instead of base-idp/server directly.

Browser usage (public client)

The browser client runs the PKCE login in a system browser tab.

lib/auth.ts
import { createBrowserBaseIdpAuth } from "base-idp/browser";

export const auth = createBrowserBaseIdpAuth({
  clientId: process.env.NEXT_PUBLIC_BASE_IDP_CLIENT_ID!,
});

Login

await auth.loginWithRedirect({
  returnTo: "/dashboard",
});

This builds the authorize URL, stores the PKCE verifier and state in session storage, and redirects to Base IdP.

Handle the callback

On your callback page:

app/auth/callback/page.tsx
"use client";
import { useEffect } from "react";
import { auth } from "@/lib/auth";

export default function CallbackPage() {
  useEffect(() => {
    auth.handleCallback().then((result) => {
      window.location.href = result.returnTo ?? "/";
    });
  }, []);

  return <p>Signing you in...</p>;
}

The handleCallback() call exchanges the code, verifies the token, and returns the tokens plus the verified principal.

Logout

await auth.logout();

Revokes the refresh token on Base IdP and clears local storage.

Server usage (verify-only)

A verify-only server only needs to check incoming tokens. No secret required.

middleware/baseIdp.ts
import type { Request, Response, NextFunction } from "express";
import { BaseIdPServerClient } from "base-idp/server";

const client = new BaseIdPServerClient({
  clientId: process.env.BASE_IDP_CLIENT_ID, // optional, pins the audience
});

export async function requireAuth(req: Request, res: Response, next: NextFunction) {
  const header = req.headers.authorization ?? "";
  const token = header.startsWith("Bearer ") ? header.slice(7) : "";
  if (!token) return res.status(401).json({ error: "missing token" });

  try {
    (req as any).principal = await client.verifyAccessToken(token);
    next();
  } catch (err) {
    res.status(401).json({ error: "invalid token" });
  }
}

What verifyAccessToken returns

type VerifiedPrincipal = {
  id: string;          // gid
  subject: string;     // sub
  email?: string;
  name?: string;
  scopes: string[];
  role?: string;
  raw: AccessClaims;   // the full claims object
};

Server usage (confidential)

A confidential server also runs the login. Pass the secret in the config.

lib/auth.ts
import { BaseIdPServerClient } from "base-idp/server";

export const auth = new BaseIdPServerClient({
  clientId: process.env.BASE_IDP_CLIENT_ID!,
  secret: process.env.BASE_IDP_CLIENT_SECRET!,
});

Authorize URL

const url = auth.authorizeUrl({
  state: "return-to=/dashboard",
});
// Redirect the user to `url`.

Exchange the code

const tokens = await auth.exchangeCode({
  code: req.query.code,
  codeVerifier: storedVerifier,
});

const principal = await auth.verifyAccessToken(tokens.access_token);

Next.js adapter

For Next.js the adapter wraps these steps in two route handlers.

app/api/auth/login/route.ts
import { createNextBaseIdpAuth } from "base-idp/next";

const auth = createNextBaseIdpAuth({
  clientId: process.env.BASE_IDP_CLIENT_ID!,
  secret: process.env.BASE_IDP_CLIENT_SECRET!,
});

export const GET = (req: Request) => auth.login(req);
app/api/auth/callback/route.ts
import { createNextBaseIdpAuth } from "base-idp/next";

const auth = createNextBaseIdpAuth(
  {
    clientId: process.env.BASE_IDP_CLIENT_ID!,
    secret: process.env.BASE_IDP_CLIENT_SECRET!,
  },
  {
    async onCallback({ principal, tokens, state }) {
      // Persist your own session cookie here.
      const response = Response.redirect(state ?? "/");
      response.headers.append(
        "Set-Cookie",
        `session=${await mint(tokens, principal)}; HttpOnly; Path=/; Secure`,
      );
      return response;
    },
  },
);

export const GET = (req: Request) => auth.callback(req);

React hooks

The React adapter exposes hooks for components that need the current principal.

"use client";
import { BaseIdpProvider, useBaseIdpSession } from "base-idp/react";

export function App({ children }) {
  return (
    <BaseIdpProvider clientId={process.env.NEXT_PUBLIC_BASE_IDP_CLIENT_ID!}>
      {children}
    </BaseIdpProvider>
  );
}

function Header() {
  const { principal, isLoading, signIn, signOut } = useBaseIdpSession();
  if (isLoading) return <Spinner />;
  if (!principal) return <button onClick={signIn}>Sign in</button>;
  return (
    <>
      <span>{principal.email}</span>
      <button onClick={signOut}>Sign out</button>
    </>
  );
}

Config

The base config type is the same across entry points. Each entry point may add a few fields of its own.

type BaseIdPConfig = {
  clientId?: string;
  secret?: string;
  issuer?: string;
  key?: string;       // legacy alias for clientId
  fetch?: FetchLike;  // override the fetch implementation (Cloudflare Workers, etc.)
};

clientId

Required for any operation that needs to talk to Base IdP (authorize URL, token exchange, client-config). Optional for pure offline verification, where it pins the audience.

secret

Server-side only. Required for the token exchange step on confidential clients.

issuer

Defaults to the production issuer. Override only for local development.

fetch

Useful for environments without a global fetch (older Node, Workers, Deno). Pass any function that matches the fetch signature.

CLI

base-idp create, init, test, and token are part of this package. See the CLI overview for details.

Where to go next

On this page