New:Microsoft Teams Notifications Are Now Available in Socket.Learn more
Get Started

@interagentic/nextjs

Package Overview
Dependencies
Maintainers
1
Versions
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@interagentic/nextjs

Next.js SDK for Interagentic auth — middleware, server auth(), route handlers, SSR provider

latest
Source
npmnpm
Version
0.1.0
Version published
Maintainers
1
Created
Source

@interagentic/nextjs

Next.js SDK for Interagentic auth — middleware, server auth(), auto route handlers, and React components. Clerk-like DX for Interagentic identity and payments: humans sign in with OAuth, agents authenticate with the identity tokens of the interagentic realm.

Quick Start

npx @interagentic/nextjs init

This interactive command will:

  • Scaffold a Next.js project (if run in an empty directory) with a demo landing page, authenticated dashboard, and sample API routes
  • Generate an ES256 keypair for client authentication and service JWT signing
  • Register your domain with id.interagentic.dev via DNS TXT verification (sends the public key to the server)
  • Write all env vars to .env
  • Create middleware and the catch-all route handler

Options:

npx @interagentic/nextjs init --domain example.com   # skip domain prompt
npx @interagentic/nextjs init --force                 # re-register and regenerate
npx @interagentic/nextjs init --jwks                  # host /.well-known/jwks.json instead of registering inline key

Manual Install

npm install @interagentic/nextjs

Environment Variables

The first four are set automatically by npx @interagentic/nextjs init:

INTERAGENTIC_AUTH_URL=https://id.interagentic.dev         # the broker; defaults to this if omitted
INTERAGENTIC_CLIENT_ID=interagentic_xxx
INTERAGENTIC_CLIENT_ASSERTION_KEY=<ES256 PEM private key>  # signs client assertions + domain JWTs
INTERAGENTIC_DOMAIN=example.com                        # your verified domain (defaults to VERCEL_PROJECT_PRODUCTION_URL)

Optional:

VariableMeaning
INTERAGENTIC_TRUSTED_BROKERSComma-separated broker hosts whose agent tokens you accept. Default id.interagentic.dev. Set it to e.g. localhost:3000 to work against a local broker.
INTERAGENTIC_AUDIENCES / INTERAGENTIC_SERVICE_DOMAINSExtra hostnames this site answers to, accepted in a token's aud.
INTERAGENTIC_TRUST_REQUEST_HOSTfalse stops accepting the host a request claims (Host / X-Forwarded-Host) as an audience. Set it when the app is reachable without a proxy that pins those headers, so a caller cannot choose the audience.
INTERAGENTIC_ISSUERIssuer of human access tokens. Defaults to INTERAGENTIC_AUTH_URL.
INTERAGENTIC_CLIENT_KEY_IDkid to put in the JWTs you sign, when the key set that verifies them carries key ids. Leave unset for a single registered key.
INTERAGENTIC_ACT_ASorg_… this site acts for (written by init --org).
INTERAGENTIC_STABLE_API_KEYiak_ key used by interagenticFetch({ as: 'apiKey' }).

The single INTERAGENTIC_CLIENT_ASSERTION_KEY does triple duty:

  • OAuth token exchange: signs client_assertion JWTs (RFC 7523 private_key_jwt)
  • Domain identity: signs X-Interagentic-Service JWTs, metadata.json requests and the tokens interagenticFetch({ as: 'domain' }) sends
  • Session cookies: AES encryption key derived from SHA-256("interagentic-session-key:" + key)

Setup (3 files)

1. Route Handler

// app/api/interagentic/[...interagentic]/route.ts
export { GET, POST } from '@interagentic/nextjs/handlers';

Handles auth/login, auth/callback, auth/me, auth/sign-out, auth/popup-done, payments/wallet, payments/ledger, payments/top-up, payments/subscriptions and payments/provider-authorizations automatically. The last two are read-only views that forward the signed-in human's access token to the broker.

Cancelling a subscription, revoking an authorization and opening the billing portal are not proxied: the broker only accepts those from the account holder's own session on it, so the components link the human to <broker>/wallet/subscriptions instead.

2. Middleware

// middleware.ts
import { interagenticMiddleware, createRouteMatcher } from '@interagentic/nextjs/server';

const isPublic = createRouteMatcher(['/', '/api/interagentic(.*)', '/api/public(.*)', '/pricing']);

export default interagenticMiddleware((auth, req) => {
  if (!isPublic(req)) auth.protect();
});

export const config = { matcher: ['/((?!_next|.*\\..*).*)'] };

Keep /api/interagentic(.*) public: the sign-in routes live there (the middleware refuses to redirect them anyway, so sign-in cannot loop).

A request that carries Authorization: Bearer is verified here exactly as auth() does it: a valid agent, API key or peer site reaches your route handler, and protect() answers a bad credential with 401 JSON plus a WWW-Authenticate header — agents never get a login redirect. Requests without that header keep the cookie behaviour (silent refresh, redirect to the login page).

3. Provider

// app/layout.tsx
import { InteragenticProvider } from '@interagentic/nextjs';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <InteragenticProvider>{children}</InteragenticProvider>
      </body>
    </html>
  );
}

The provider is a client component: it loads the session from /api/interagentic/auth/me on mount. Pass authUrl when your broker is not https://id.interagentic.dev (local development) — popup messages from human-in-the-loop flows are only accepted from that origin:

<InteragenticProvider authUrl={process.env.NEXT_PUBLIC_INTERAGENTIC_AUTH_URL}>

Server Auth

Route Handlers & Server Components

import { auth } from '@interagentic/nextjs/server';

export async function POST(req: Request) {
  const { subject } = await auth(req).protect();
  const order = await createOrder(await req.json(), subject);
  return Response.json(order);
}

auth(req) resolves who is calling. Pass the request in route handlers (and anywhere outside a Next.js request scope); auth() without arguments works in server components and server actions. The result is memoized per request.

const session = await auth(req);

session.isAuthenticated; // boolean
session.subject;         // string | null — the acting identity (the token's `sub`)
session.namespace;       // string | null — agent namespace that signed the request
session.type;            // 'human' | 'bot' | null
session.credential;      // 'agent-token' | 'access-token' | 'api-key' | 'domain-token' | 'session' | null
session.orgId;           // string | undefined — org the caller acts for
session.humanId;         // string | undefined — pairwise human id (see linkage below)
session.scopes;          // string[] — API key / access token scopes
session.broker;          // string | null — broker that vouches for the identity
session.keyId;           // string | null
session.email;           // string | null (humans)
session.getToken();      // the raw credential

session.protect();           // 401 for Bearer callers, login redirect otherwise
session.redirectToSignIn();  // explicit redirect
await session.getLinkage();  // ask the broker whether a human is behind an agent

subject is what you store as the owner of anything the caller creates:

Callersubjectnamespace
Agentinteragentic://id.interagentic.dev/brave-foxbrave-fox
Agent acting for an orginteragentic://id.interagentic.dev/org_…brave-fox (+ orgId)
iak_ API keyinteragentic://id.interagentic.dev/<ns><ns>
Signed-in human (cookie or access token)an opaque id, pairwise for this sitenull
Another site's domain tokenpeer.examplepeer.example

userId, namespaceId and signerNamespace still exist as deprecated aliases (subject, orgId ?? namespace, and the signer of an org call), so existing handlers keep compiling.

What auth() accepts

CredentialChecks
Agent token (Authorization: Bearer)Broker read from iss and matched against INTERAGENTIC_TRUSTED_BROKERS; key set from https://<broker>/<ns>/jwks.json; EdDSA/ES256/RS256; aud names this site; iat/exp/jti present, at most 5 minutes old and exp - iat <= 300. Acting for an org is confirmed with the broker's verify-member endpoint.
Human access tokentyp: at+jwt, issued by your broker to this site's OAuth client. Refresh tokens, ID tokens and tokens minted for another client are rejected.
iak_ API keyIntrospected at the broker (cached 30 s).
Another site's domain tokenVerified against that domain's key set; aud must name this site.
Session cookieEncrypted __interagentic_session / __interagentic_refresh, refreshed silently.

A request that carries Authorization: Bearer never falls back to the cookie session, and a bpk_ (BotParty) key is never accepted.

Is a human behind the agent?

Linkage is never read from the agent's token — the agent signs its own token, so a linked claim in it means nothing. Ask the broker instead:

const session = await auth(req).protect();
const { linked, humanId, permissions } = await session.getLinkage();

if (!linked) {
  return Response.json({ error: 'user_required' }, { status: 403 });
}
await recordOrder(humanId);   // stable for this site, unrelated at other sites

getLinkage() calls GET https://<broker>/<ns>/metadata.json with a domain JWT signed by your site key, and caches the answer for 10 seconds. It is lazy: nothing is fetched until you ask (or pass auth(req, { linkage: true }), which also fills session.hasLinkedUser and session.humanId before you read them). When the broker cannot answer it throws InteragenticLinkageError rather than reporting "not linked".

protect()

  • Authenticated: returns the session (subject typed as string).
  • Bearer credential that does not check out: throws InteragenticAuthError. Next.js turns it into a 401 in a route handler; in the middleware it becomes a 401 JSON body. Catch it and return error.toResponse() if you want that JSON body from the handler too.
  • No credential at all: redirects to the login page (unchanged behaviour).

Middleware Auth Object

Inside interagenticMiddleware, the auth callback receives:

interface MiddlewareAuthObject {
  isAuthenticated: boolean;
  subject: string | null;
  namespace: string | null;
  type: 'human' | 'bot' | null;
  credential: 'agent-token' | 'access-token' | 'api-key' | 'domain-token' | 'session' | null;
  humanId?: string;   // humans only; agents need auth(req).getLinkage()
  orgId?: string;
  scopes: string[];
  email: string | null;
  meta: Record<string, string>;
  userId: string | null;       // deprecated alias of subject
  namespaceId: string | null;  // deprecated alias of orgId ?? namespace
  protect(): MiddlewareAuthObject;  // 401 JSON for Bearer callers, login redirect otherwise
  redirectToSignIn(): never;
}

Returning a Response from the callback sends it as-is (redirect, rewrite or error), so you can answer requests yourself instead of calling protect().

Creating Paid Endpoints

Use createPaymentGateway() to charge for API access via the x402 protocol. The gateway handles verification, payment creation, and settlement automatically.

Units: All amounts are integers in USDC atomic units (6 decimals). 1 USDC = 1,000,000 units.

Setup

import { createPaymentGateway } from '@interagentic/nextjs/server';

const gateway = createPaymentGateway({ serviceName: 'My API' });

Domain and private key are read from env vars automatically (INTERAGENTIC_DOMAIN, INTERAGENTIC_CLIENT_ASSERTION_KEY). On Vercel, domain falls back to VERCEL_PROJECT_PRODUCTION_URL if INTERAGENTIC_DOMAIN is not set.

You can override any of these in the config object:

const gateway = createPaymentGateway({
  domain: 'custom.example.com',
  serviceName: 'My API',
  privateKey: process.env.MY_CUSTOM_KEY!,
});

Pay-per-call

Charge a flat fee per request:

// app/api/bots/pay-per-call/route.ts
export async function POST(req: Request) {
  const check = await gateway.requirePayment(req, {
    amount: 10_000,
    description: 'Pay-per-call API request ($0.01)',
  });

  if (!check.authorized) return check.response; // 402 with payTo link

  const result = { message: 'Paid request successful!' };

  await gateway.claim(check, {
    amount: 10_000,
    description: 'Pay-per-call API request',
  });

  return Response.json(result);
}

Dynamic payment (budget + actual cost)

Request a budget upfront, bill actual cost after processing:

// app/api/bots/dynamic-payment/route.ts
export async function POST(req: Request) {
  const check = await gateway.requireBudget(req, {
    estimated: 5_000_000,
    description: 'AI inference — estimated cost ($5.00)',
  });

  if (!check.authorized) return check.response; // 402

  const actualCost = await doExpensiveWork();

  await gateway.claim(check, {
    amount: actualCost,
    description: `AI inference — actual cost ($${(actualCost / 1_000_000).toFixed(6)})`,
  });

  return Response.json({ message: 'Done!', charged: actualCost });
}

Payment flow summary

  • Caller hits endpoint
  • requirePayment() checks for an approved payment via the facilitator, authenticating as your domain (X-Interagentic-Service) and forwarding the caller's own token as the payer
  • If not approved: returns 402 with PAYMENT-REQUIRED header and a JSON body containing a payTo link to a /humans/{flowId} multi-step approval flow
  • Human visits the payTo link, completes login + top-up + approval
  • Caller retries the same request (no special headers needed)
  • requirePayment() finds the approved payment, returns authorized: true
  • After processing, claim() settles the payment via the facilitator

Calling Other Services

Your service has a domain identity — the ES256 key in INTERAGENTIC_CLIENT_ASSERTION_KEY that interagentic init registered with the IdP. To call another Interagentic service, use interagenticFetch, which attaches the right Authorization header. Pick a mode explicitly:

import { interagenticFetch } from '@interagentic/nextjs/server';

// 1. domain — act AS your service (signed domain JWT). The peer's auth() sees
//    type:'bot', namespaceId=<your domain>. Use for backend / machine calls.
const res = await interagenticFetch('https://peer.interagentic.dev/api/v1/thing', {
  interagenticAuth: { as: 'domain' },
});

// 2. user — act ON BEHALF OF the signed-in user (forwards their access token).
//    The peer sees the end user, not your service.
const res2 = await interagenticFetch('https://peer.interagentic.dev/api/v1/thing', {
  interagenticAuth: { as: 'user' },
});

// 3. apiKey — use a stable iak_ key (CI / standalone backend identities).
const res3 = await interagenticFetch('https://peer.interagentic.dev/api/v1/thing', {
  interagenticAuth: { as: 'apiKey' }, // reads INTERAGENTIC_STABLE_API_KEY
});

How the peer verifies it: the receiving service's auth() resolves the Authorization: Bearer token — a domain JWT is verified against the issuer's key set on the broker (no shared secret, no extra hosting), and a iak_ key via introspection.

Every token is bound to its target: interagenticFetch sets aud to the target's hostname, and a peer running this package rejects a domain token whose aud is not one of its own hostnames. That is what stops a service you call from replaying your token somewhere else.

{ as: 'user' } forwards the signed-in human's access token, which the broker issued for your OAuth client — use it for calls to the broker or to your own services, not to third parties (they bind access tokens to their own client). An agent's token is never forwarded: it is addressed to your site, so { as: 'auto' } falls back to the domain identity for agent-authenticated requests.

Lower-level helpers are also exported: signDomainToken(opts) returns just the JWT (aud from opts.audience), and interagenticAuthHeaders(mode, targetUrl?) returns just the { Authorization } header for use with non-fetch clients (axios, the AI SDK provider config, etc.).

LLM inference is the exception. llm.interagentic.dev/v1/* routes straight to LiteLLM, which needs an sk- virtual key — not a domain JWT. Mint one once with npx @interagentic/nextjs add-service llm (it authenticates to the management API with your domain identity and writes LLM_API_KEY to .env). See docs/SERVICE-TO-SERVICE.md.

Client Components

All hooks and components from @interagentic/react are re-exported:

import {
  useAuth, useUser, useWallet, useLedger,
  SignedIn, SignedOut, HasLinkedUser,
  UserButton, NamespaceBadge,
  WalletBalance, TransactionList, TopUpButton,
  SubscriptionList, AuthorizationList, ManageBillingButton,
  ActionButton, SignOutButton,
} from '@interagentic/nextjs';

<SubscriptionList> and <AuthorizationList> read through the payments/* routes of the catch-all handler; they and <ManageBillingButton> link the human to the broker for anything that changes billing.

<SpendingControls> was removed in 0.1.0: it called a spending-policy endpoint that neither this package nor the broker implements.

Example Page

import { SignedIn, SignedOut, UserButton, WalletBalance } from '@interagentic/nextjs';

export default function Dashboard() {
  return (
    <div>
      <SignedIn>
        <UserButton />
        <WalletBalance />
      </SignedIn>
      <SignedOut>
        <a href="/api/interagentic/auth/login">Sign in</a>
      </SignedOut>
    </div>
  );
}

Session Strategy

  • Humans: encrypted cookies (__interagentic_session 15 min, __interagentic_refresh 30 d) encrypted with a key derived from INTERAGENTIC_CLIENT_ASSERTION_KEY; the middleware and the route handlers refresh them silently
  • Agents: per-namespace key sets at https://<broker>/<ns>/jwks.json, cached in memory and re-fetched when a key rotates
  • Human access tokens: the broker's key set at /.well-known/jwks.json
  • Peers: the calling domain's key set, resolved by the broker
  • No state: nothing about a caller is stored; every request is verified from the token (plus a 10 s linkage cache and a 30 s org-membership cache)

Demo Project

When you run npx @interagentic/nextjs init in an empty directory, it scaffolds a complete demo with:

  • Landing page (/) -- sign in button, feature cards
  • Dashboard (/dashboard) -- protected route showing all SDK components:
    • <UserButton>, <NamespaceBadge>, <WalletBalance>, <TransactionList>
    • <TopUpButton>
    • API tester panel with buttons to call each sample route
    • Terminal commands for testing with interagentic curl
  • Sample API routes:
    • GET /api/bots/open -- accepts any verified caller
    • GET /api/bots/user-required -- requires a human behind the agent (asked of the broker)
    • POST /api/bots/pay-per-call -- $0.10/call with payment authorization
    • POST /api/bots/dynamic-payment -- $5 authorization, $0.20/call

Exports

PathContents
@interagentic/nextjsInteragenticProvider, all React hooks + components
@interagentic/nextjs/serverauth(), currentUser(), InteragenticAuthError, interagenticMiddleware, createRouteMatcher, session utils, interagenticFetch, interagenticAuthHeaders, signDomainToken, createPaymentGateway, createJwksResponse, verifyBearerToken
@interagentic/nextjs/handlersGET, POST catch-all route handlers
@interagentic/nextjs/jwksverifyBearerToken, verifyAccessToken, verifyDomainToken, introspectToken, JWKS cache hooks

Keywords

interagentic

FAQs

Package last updated on 21 Sep 2026

Related posts