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 variable | Value |
|---|---|
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY | publishable_key from /_twin/config |
CLERK_JWT_KEY | jwt_key from /_twin/config |
NEXT_PUBLIC_CLERK_PROXY_URL | proxy_url from /_twin/config |
NEXT_PUBLIC_CLERK_JS_URL | the simulator’s /clerk.js |
NEXT_PUBLIC_CLERK_UI_URL | the simulator’s /clerk-ui.js |
CLERK_API_URL | the simulator’s origin |
CLERK_SECRET_KEY | any sk_ value |
NEXT_PUBLIC_CLERK_SIGN_IN_URL, NEXT_PUBLIC_CLERK_SIGN_UP_URL | your 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.