Skip to Content
SimulatorsClerkBrowser companion

Simulators

Browser companion

The browser companion lets a @clerk/nextjs app sign users in, switch organizations and manage members against the simulator, on the same users, organizations and memberships the Backend API serves. Its lookalike sign-in and sign-up screens offer email sign-in and simulated Continue with GitHub and Continue with Google buttons. Its screens also create and select organizations, invite members, change roles and remove members. No provider, OAuth exchange or email is involved.

The companion needs three things at boot: an RSA private key of your own, your site’s origin and the simulator’s own origin as the browser reaches it (CLERK_TWIN_ISSUER). The simulator reads the key from a file on your machine, which the Docker runner does not mount, so the example runs it on the native runner. Clerk’s libraries refuse the publishable key of a host with no dot, such as localhost, so the issuer uses 127.0.0.1. The example uses port 8123: use any free port in both the flag and the issuer.

KEY="$(mktemp -d)/clerk-twin.pem"
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out "$KEY"
chmod 600 "$KEY"
export RYSTIC_URL=$(rystic run clerk-twin --runner native -d --port 8123 --scenario clerk_acme_tenant --env CLERK_TWIN_PRIVATE_KEY_FILE="$KEY" --env CLERK_TWIN_SITE_ORIGIN=http://localhost:3000 --env CLERK_TWIN_ISSUER=http://127.0.0.1:8123 --json | jq -r .data.url)
curl -s "$RYSTIC_URL/_twin/config" | jq -c '{issuer, proxy_url}'
# -> {"issuer":"http://127.0.0.1:8123","proxy_url":"http://127.0.0.1:8123"}
curl -s -o /dev/null -w '%{http_code}\n' "$RYSTIC_URL/clerk.js"
# -> 200

Configure your site

GET /_twin/config returns the publishable key, the public PEM and the proxy URL. Configure the site from them and the simulator’s own paths:

Site variableValue
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYpublishable_key from /_twin/config
CLERK_JWT_KEYjwt_key from /_twin/config
NEXT_PUBLIC_CLERK_PROXY_URLproxy_url from /_twin/config
NEXT_PUBLIC_CLERK_JS_URLthe simulator’s /clerk.js
NEXT_PUBLIC_CLERK_UI_URLthe simulator’s /clerk-ui.js
CLERK_API_URLthe simulator’s origin
CLERK_SECRET_KEYany sk_ value
NEXT_PUBLIC_CLERK_SIGN_IN_URL, NEXT_PUBLIC_CLERK_SIGN_UP_URLyour own sign-in and sign-up pages

Without a key file, /_twin/* and the JWKS (/.well-known/jwks.json, /jwks, /v1/jwks) answer 404, while clerk.js and the Backend API serve either way.

Sign-in

Any address signs in, and an unknown one creates a user. Signing up asks for consent. Signing in as an address with a pending invitation joins its organization, with no email sent. Continue with GitHub signs in as github@example.test and Continue with Google as google@example.test; the first use asks for the site’s consent checkbox. No provider is contacted.

Differs from Clerk: sign-in is passwordless, and any address works. There is no password, MFA, OAuth, email or SMS delivery.

Sessions

A session is an RS256 token carrying the user’s current public metadata and active organization, so a role your backend changes shows on the next token refresh. Sessions last 24 hours of the simulator’s clock, and a reset ends every session. A token’s iat, nbf and exp are stamped on the wall clock your site verifies them against. exp is that wall time plus the session’s remaining life on the simulator’s clock. Tokens therefore verify however long the simulator sits idle and wherever its clock is pinned.

Differs from Clerk: Clerk’s session tokens are short-lived, with exp set by the instance’s token lifetime setting. The simulator’s exp runs to the session’s end.

The handshake

When a token has expired, or a visit arrives from another origin, the SDK middleware sends the browser to the proxy URL’s /v1/client/handshake. The simulator sends it back to redirect_url, which must be on the site origin, with the session renewed while it is live and signed out once it has ended. Without the sign-in and sign-up URLs above, the SDK sends a signed-out visitor to Clerk’s hosted Account Portal instead.

Origins and private metadata

The companion’s JSON routes return 403 to a request whose Origin header names any origin but the configured site’s. clerk.js, clerk-ui.js and clerk.css load from any origin. Private metadata never reaches the browser, while webhooks and the Backend API carry it.

What it does not do

The companion is a test surface, not Clerk’s Frontend API. It serves only the @clerk/nextjs surface a Next.js app uses, and its sign-in is passwordless (Sign-in). Organization profile custom pages mount through the site’s own callbacks. Its roles are org:admin and org:member, and it refuses to remove an organization’s last admin.

Differs from Clerk: other Clerk components render an unsupported message.

Session revocation is per process. A reset ends every session the process issued. A fresh process with the same signing key cannot tell an old token from a new one, so use a new key when you reuse seeded sessions across runs.

Last updated on