Skip to main content

    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 → ActionsFlowsLogin → 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 → AuthAdd 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 custom uid claim 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:

    1. Your billing webhook → Auth0 Management API → update app_metadata.plan.
    2. Call api.accessToken.setCustomClaim on 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 uid custom 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 reducing ttl.
    • Option B: Auth0 session revoke invalidates subsequent tokens; Behest JWKS cache (5 min) determines propagation.

    See also

    Enterprise Token FinOps: Enforce hard budgets and attribute costs per session.

    Learn more