Auth0 → Behest
Map Auth0 users to Behest users. Two options: mint via API key, or trust Auth0's JWKS directly for zero-hop auth.
Option A — API-key mint from backend
1. Backend (Node/Express)
ts
import { expressjwt } from "express-jwt";
import jwksRsa from "jwks-rsa";
import { Behest } from "@behest/client-ts";
const checkJwt = expressjwt({
secret: jwksRsa.expressJwtSecret({
jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`,
}) as any,
audience: process.env.AUTH0_AUDIENCE,
issuer: `https://${process.env.AUTH0_DOMAIN}/`,
algorithms: ["RS256"],
});
const behest = new Behest(); // reads BEHEST_KEY + BEHEST_BASE_URL from env
app.post("/api/behest/token", checkJwt, async (req: any, res) => {
const userId = req.auth.sub; // e.g. "auth0|abc123"
const tier = req.auth["https://myapp.com/plan"] ?? 1;
try {
const { token, ttl, sessionId, expiresAt } = await behest.auth.mint({
user_id: userId,
tier,
ttl: 900,
});
res.json({ token, ttl, sessionId, expiresAt });
} catch (err) {
res.status(500).json({ error: String(err) });
}
});2. Client
ts
import { useAuth0 } from "@auth0/auth0-react";
import { useMemo } from "react";
import OpenAI from "openai";
type TokenBundle = {
token: string;
ttl: number;
sessionId: string;
expiresAt: number;
};
export function useOpenAI() {
const { getAccessTokenSilently } = useAuth0();
return useMemo(() => {
let cached: TokenBundle | null = null;
return async () => {
const now = Math.floor(Date.now() / 1000);
if (!cached || cached.expiresAt - now < 60) {
const auth0Token = await getAccessTokenSilently();
const r = await fetch("/api/behest/token", {
method: "POST",
headers: { Authorization: `Bearer ${auth0Token}` },
});
cached = (await r.json()) as TokenBundle;
}
return new OpenAI({
apiKey: cached.token,
baseURL: `${import.meta.env.VITE_BEHEST_BASE_URL}/v1`,
dangerouslyAllowBrowser: true,
defaultHeaders: { "X-Session-Id": cached.sessionId },
});
};
}, [getAccessTokenSilently]);
}Option B — Trust Auth0's JWKS directly
Behest's Kong plugin can verify Auth0-issued JWTs. Saves you the mint hop.
1. Add a custom Action in Auth0 to inject Behest claims
Auth0 dashboard → Actions → Flows → Login → add an action:
js
exports.onExecutePostLogin = async (event, api) => {
api.accessToken.setCustomClaim("tid", "<your-behest-tenant-id>");
api.accessToken.setCustomClaim("pid", "<your-behest-project-id>");
api.accessToken.setCustomClaim("uid", event.user.user_id);
api.accessToken.setCustomClaim(
"tier",
event.user.app_metadata.plan ?? "free"
);
};2. Register Auth0 as a trusted issuer in Behest
Dashboard → Project → Auth → Add trusted issuer:
- Issuer:
https://<your-auth0-domain>/ - JWKS URL:
https://<your-auth0-domain>/.well-known/jwks.json - Audience: your Auth0 API audience
- Claim mapping:
sub→uid(or use customuidclaim from the action)
3. Client sends Auth0 token directly
ts
import OpenAI from "openai";
import { useAuth0 } from "@auth0/auth0-react";
export async function getOpenAI(
getAccessTokenSilently: ReturnType<typeof useAuth0>["getAccessTokenSilently"]
) {
const token = await getAccessTokenSilently();
return new OpenAI({
apiKey: token,
baseURL: `${import.meta.env.VITE_BEHEST_BASE_URL}/v1`,
dangerouslyAllowBrowser: true,
});
}No /api/behest/token endpoint, no Behest key. Auth0 is the sole token minter.
Syncing tiers
When a user upgrades:
- Your billing webhook → Auth0 Management API → update
app_metadata.plan. - Call
api.accessToken.setCustomClaimon next login, or force a silent re-auth so the new tier lands in the JWT.
Machine-to-machine
For M2M clients (CI, server-to-server scripts):
- Use Auth0 Client Credentials flow to get an access token.
- Ensure the M2M client has a
uidcustom claim set to a stable identifier (e.g., client id). - Forward that token to Behest exactly as you would a user token.
Organizations
Auth0 Organizations → Behest pid: usually one Behest project per Auth0 org. Set pid via the Action based on event.organization.id.
Logout
- Option A: Behest JWT expires at
ttl; no action needed. Force instant revoke by reducingttl. - Option B: Auth0 session revoke invalidates subsequent tokens; Behest JWKS cache (5 min) determines propagation.