
Security News
Lovable’s OJ Rewrites Vite’s Dev Server in Rust as AI Lowers the Cost of Forking Open Source
Lovable’s OJ rewrites Vite’s dev server in Rust, reducing memory use and preview times as AI lowers the cost of open source reimplementation.
@interagentic/nextjs
Advanced tools
Next.js SDK for Interagentic auth — middleware, server auth(), route handlers, SSR provider
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.
npx @interagentic/nextjs init
This interactive command will:
.envOptions:
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
npm install @interagentic/nextjs
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:
| Variable | Meaning |
|---|---|
INTERAGENTIC_TRUSTED_BROKERS | Comma-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_DOMAINS | Extra hostnames this site answers to, accepted in a token's aud. |
INTERAGENTIC_TRUST_REQUEST_HOST | false 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_ISSUER | Issuer of human access tokens. Defaults to INTERAGENTIC_AUTH_URL. |
INTERAGENTIC_CLIENT_KEY_ID | kid 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_AS | org_… this site acts for (written by init --org). |
INTERAGENTIC_STABLE_API_KEY | iak_ key used by interagenticFetch({ as: 'apiKey' }). |
The single INTERAGENTIC_CLIENT_ASSERTION_KEY does triple duty:
client_assertion JWTs (RFC 7523 private_key_jwt)X-Interagentic-Service JWTs, metadata.json requests and the tokens interagenticFetch({ as: 'domain' }) sendsSHA-256("interagentic-session-key:" + key)// 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.
// 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).
// 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}>
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:
| Caller | subject | namespace |
|---|---|---|
| Agent | interagentic://id.interagentic.dev/brave-fox | brave-fox |
| Agent acting for an org | interagentic://id.interagentic.dev/org_… | brave-fox (+ orgId) |
iak_ API key | interagentic://id.interagentic.dev/<ns> | <ns> |
| Signed-in human (cookie or access token) | an opaque id, pairwise for this site | null |
| Another site's domain token | peer.example | peer.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.
auth() accepts| Credential | Checks |
|---|---|
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 token | typ: 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 key | Introspected at the broker (cached 30 s). |
| Another site's domain token | Verified against that domain's key set; aud must name this site. |
| Session cookie | Encrypted __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.
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".
subject typed as string).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.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().
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.
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!,
});
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);
}
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 });
}
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 payer402 with PAYMENT-REQUIRED header and a JSON body containing a payTo link to a /humans/{flowId} multi-step approval flowpayTo link, completes login + top-up + approvalrequirePayment() finds the approved payment, returns authorized: trueclaim() settles the payment via the facilitatorYour 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 ansk-virtual key — not a domain JWT. Mint one once withnpx @interagentic/nextjs add-service llm(it authenticates to the management API with your domain identity and writesLLM_API_KEYto.env). See docs/SERVICE-TO-SERVICE.md.
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.
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>
);
}
__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 silentlyhttps://<broker>/<ns>/jwks.json, cached in memory and re-fetched when a key rotates/.well-known/jwks.jsonWhen you run npx @interagentic/nextjs init in an empty directory, it scaffolds a complete demo with:
/) -- sign in button, feature cards/dashboard) -- protected route showing all SDK components:
<UserButton>, <NamespaceBadge>, <WalletBalance>, <TransactionList><TopUpButton>interagentic curlGET /api/bots/open -- accepts any verified callerGET /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 authorizationPOST /api/bots/dynamic-payment -- $5 authorization, $0.20/call| Path | Contents |
|---|---|
@interagentic/nextjs | InteragenticProvider, all React hooks + components |
@interagentic/nextjs/server | auth(), currentUser(), InteragenticAuthError, interagenticMiddleware, createRouteMatcher, session utils, interagenticFetch, interagenticAuthHeaders, signDomainToken, createPaymentGateway, createJwksResponse, verifyBearerToken |
@interagentic/nextjs/handlers | GET, POST catch-all route handlers |
@interagentic/nextjs/jwks | verifyBearerToken, verifyAccessToken, verifyDomainToken, introspectToken, JWKS cache hooks |
FAQs
Next.js SDK for Interagentic auth — middleware, server auth(), route handlers, SSR provider
We found that @interagentic/nextjs demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Lovable’s OJ rewrites Vite’s dev server in Rust, reducing memory use and preview times as AI lowers the cost of open source reimplementation.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.