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-07-19

@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: 'c_xxx',
  clientSecret: process.env.AUTHPI_CLIENT_SECRET, // omit for public clients (SPAs)
  redirectUri: 'https://app.example.com/callback',
  // resources: ['https://api.example.com/reports'], // after configuring RFC 8707 policy
});

issuerUrl is your issuer’s URL (or your custom domain). Confidential clients pass a clientSecret; public clients omit it and rely on PKCE. After adding an exact resource to both the issuer catalog and registered-client allowlist, optional resources values are sent as repeated RFC 8707 resource parameters during authorization, code exchange, and refresh.

The login flow

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

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

session.set('oauth', auth);
redirect(auth.url);

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

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

2. Handle the callback. Use the stored authorization object to validate state, exchange the code, and verify the ID-token nonce when an ID token is present:

const agent = await idp.exchangeCallback(callbackUrl, await session.get('oauth'));
await session.set('tokens', agent.tokens);

The authorization object includes the effective resources list. Store the whole object: exchangeCallback repeats that exact list during code exchange. To select a refresh subset explicitly, pass { resources: ['https://api.example.com/reports'] } to refresh or createAgent. An empty list omits the parameter. Automatic refresh from createAgent also omits resource unless that call explicitly supplies resources, even when the client has an authorization default; under an optional policy the server then retains the refresh session’s selected set. A policy with required: true needs explicit createAgent resources and rejects omission. TokenSet intentionally has no resource field.

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: 'c_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