SDKs

IdP SDK — TypeScript

Add AuthPI login to TypeScript backends with @authpi/idp — the OIDC authorization-code flow with PKCE, client_credentials for M2M, token refresh, and per-organization authorization checks.

Last updated 2026-06-20

@authpi/idp is the official TypeScript SDK for authenticating against your AuthPI issuer: it drives the OIDC authorization-code flow with PKCE, supports client_credentials for machine-to-machine auth, exchanges and refreshes tokens, and gives you an authenticated agent object with the caller’s identity and per-organization permissions.

It runs on Node.js 18+, Bun, Deno, and Cloudflare Workers.

Install

npm install @authpi/idp

Initialize

import { IdpClient } from '@authpi/idp';

const idp = new IdpClient({
  issuerUrl: 'https://idp.authpi.com/i_4r8w2k9m5x1p7q3e6t0y2u4i8',
  clientId: 'cli_xxx',
  clientSecret: process.env.AUTHPI_CLIENT_SECRET, // omit for public clients (SPAs)
  redirectUri: 'https://app.example.com/callback',
});

issuerUrl is your issuer’s URL (or your custom domain). Confidential clients pass a clientSecret; public clients omit it and rely on PKCE.

The login flow

1. Send the user to AuthPI. createAuthorizationUrl generates the PKCE verifier, state, and nonce — store them in the user’s session, then redirect:

const { url, codeVerifier, state, nonce } = await idp.createAuthorizationUrl({
  scopes: ['openid', 'profile', 'email'],
});

session.set('oauth', { codeVerifier, state, nonce });
redirect(url);

To issue tokens restricted to one organization, include org in the authorization URL options:

const { url, codeVerifier, state, nonce } = await idp.createAuthorizationUrl({
  scopes: ['openid', 'profile', 'email'],
  org: 'org_0kfz3m8q1w5e9r2t6y4u7i3o5',
});

2. Handle the callback. After checking that the returned state matches the one you stored, exchange the code for an authenticated agent. The exchange goes directly to the token endpoint over TLS, and PKCE binds it to the verifier from step 1:

const agent = await idp.exchangeCode(code, codeVerifier);
await session.set('tokens', agent.tokens);

3. Use the agent. It carries the user’s identity and organization memberships, with an authorization helper that checks scopes within an organization (driven by the organizations claim):

if (agent.hasAccessIn('org_0kfz3m8q1w5e9r2t6y4u7i3o5', 'write', 'projects')) {
  // the user can write to projects in that organization
}

Sessions and refresh

Rebuild an agent from stored tokens on subsequent requests — createAgent refreshes expired access tokens automatically and hands you the rotated tokens through onRefresh:

const agent = await idp.createAgent(await session.get('tokens'), {
  onRefresh: async (newTokens) => {
    await session.set('tokens', newTokens);
  },
  onRefreshError: async (error) => {
    await session.destroy(); // session expired — send the user back to login
  },
});

Refresh tokens rotate on use, so always persist what onRefresh gives you. Refresh timing is configurable (autoRefresh, refreshBufferSeconds), and the agent exposes expiresAt / expiresIn / isExpired() for your own checks. Organization claims in refreshed tokens are recomputed at every refresh — membership changes propagate within the access-token TTL (the propagation contract).

Machine-to-machine

For server-to-server auth, use the client_credentials flow with an M2M client (or an agent identity) — no redirectUri needed:

const idp = new IdpClient({
  issuerUrl: 'https://idp.authpi.com/i_4r8w2k9m5x1p7q3e6t0y2u4i8',
  clientId: 'cli_m2m_xxx',
  clientSecret: process.env.AUTHPI_CLIENT_SECRET,
});

const agent = await idp.clientCredentials({ scopes: ['users:read'] });
agent.hasAccess('read', 'users'); // token-level scopes — no organizations

See machine-to-machine auth for choosing between M2M clients, org API keys, and agent identities.

Next steps