Part VII · Reach and shipT3 Connect
Chapter 35source checked

T3 Connect: OAuth, DPoP, relay, and tunnel

T3 Connect uses a Clerk account credential and a device-held DPoP key to authorize relay control-plane operations, links a local environment through a signed proof, provisions a Cloudflare tunnel, then gives the client a direct DPoP-bound connection to that environment. The relay authorizes, provisions, and brokers setup; normal T3 traffic does not transit it.

What this chapter resolves
  • Trace Clerk OAuth from the hosted handoff page through PKCE callback and stored CLI credential.
  • Separate a device DPoP key, relay DPoP access token, environment bootstrap credential, environment access token, and WebSocket ticket.
  • Explain environment link registration and managed Cloudflare tunnel provisioning without turning the relay into a fictional data proxy.
  • Identify the control-plane and launch calls that involve the relay versus the direct environment data plane.

T3 Connect is a connection-launch system, not a hosted copy of an environment. It uses a cloud account to discover and authorize an environment, arranges a managed endpoint when requested, and gives a client credentials to enter that environment. Once connected, the selected environment—not the relay—continues to own T3 HTTP, WebSocket, orchestration, terminal, and project traffic.

1. Clerk establishes account authority; the CLI uses hosted OAuth handoff

Web, desktop, and mobile use the configured Clerk application for cloud account state. For the headless CLI, the OAuth public client uses PKCE: the process creates a verifier, challenge, and state, starts either a loopback callback listener or an out-of-band prompt, and exchanges the resulting authorization code at Clerk’s token endpoint. The CLI stores the returned OAuth credential in its environment secret store; t3 connect login does not enable remote exposure.

The browser starts at the hosted /connect page, not Clerk’s authorize endpoint directly. The request carries state and PKCE challenge in the fragment. After the hosted page has a Clerk session, it forwards the authorization request with those parameters intact. This avoids a signed-out Clerk redirect losing the authorize request. A loopback request supplies a port and returns to http://127.0.0.1:<port>/callback; an SSH or --headless flow instead uses hosted /connect/callback, displays code.state, and the terminal checks the state before exchanging the code.

packages/shared/src/connectAuth.ts:19–104 ↗verbatim · typescript · 01950805
/**
 * Requested at authorize time by the hosted page and honored by the CLI's
 * token exchange; keep both sides on this single definition.
 */
export const CONNECT_OAUTH_SCOPES = ["openid", "profile", "email"] as const;
 
export interface ConnectAuthorizeRequest {
  readonly state: string;
  readonly challenge: string;
  /**
   * Present when a loopback CLI initiated the request: the hosted /connect
   * page then asks Clerk to redirect the authorization code straight to
   * `http://127.0.0.1:<port>/callback` instead of the hosted callback page.
   */
  readonly loopbackPort?: number;
}
 
/**
 * The URL the CLI prints for the user to open in a browser. `state` and
 * `code_challenge` ride the fragment so they never reach the hosted app's
 * server or CDN logs; neither is a secret.
 *
 * Both CLI flows route through the hosted /connect page rather than hitting
 * Clerk's /oauth/authorize directly: a signed-out browser sent straight to
 * /oauth/authorize goes through Clerk's sign-in redirect, which does not
 * reliably preserve the authorize query parameters (state, response_type,
 * code_challenge). The hosted page waits for a Clerk session first, then
 * forwards the request with the parameters intact.
 */
export function buildConnectAuthorizeRequestUrl(input: {
  readonly hostedAppUrl: string;
  readonly state: string;
  readonly challenge: string;
  readonly loopbackPort?: number;
}): string {
  const url = new URL(CONNECT_AUTHORIZE_PATH, input.hostedAppUrl);
  url.hash = new URLSearchParams([
    [CONNECT_AUTH_STATE_PARAM, input.state],
    [CONNECT_AUTH_CHALLENGE_PARAM, input.challenge],
    ...(input.loopbackPort === undefined
      ? []
      : [[CONNECT_AUTH_PORT_PARAM, String(input.loopbackPort)] as [string, string]]),
  ]).toString();
  return url.toString();
}
 
export function readConnectAuthorizeRequest(url: URL): ConnectAuthorizeRequest | null {
  const params = readHashParams(url);
  const state = params.get(CONNECT_AUTH_STATE_PARAM)?.trim() ?? "";
  const challenge = params.get(CONNECT_AUTH_CHALLENGE_PARAM)?.trim() ?? "";
  if (!state || !challenge) {
    return null;
  }
  const port = params.get(CONNECT_AUTH_PORT_PARAM);
  if (port === null) {
    return { state, challenge };
  }
  // A present-but-invalid port means the link was corrupted; reject the whole
  // request rather than silently downgrading a loopback flow to the
  // out-of-band one, which would strand the waiting CLI.
  const loopbackPort = parseLoopbackPort(port.trim());
  if (loopbackPort === null) {
    return null;
  }
  return { state, challenge, loopbackPort };
}
 
function parseLoopbackPort(value: string): number | null {
  if (!/^\d{1,5}$/.test(value)) {
    return null;
  }
  const port = Number(value);
  return port >= 1 && port <= 65535 ? port : null;
}
 
/**
 * Redirect URI for the CLI's local callback listener. Must stay in sync with
 * the redirect URI registered on the Clerk CLI OAuth application.
 */
export function connectLoopbackRedirectUri(port: number): string {
  return `http://127.0.0.1:${port}${CONNECT_LOOPBACK_CALLBACK_PATH}`;
}
 
export function connectCallbackUrl(hostedAppUrl: string): string {
  return new URL(CONNECT_CALLBACK_PATH, hostedAppUrl).toString();
}
Read this as: The shared URL builder keeps PKCE state and challenge in the hosted-page fragment and supplies separate loopback and hosted callback destinations.
apps/server/src/cloud/CliTokenManager.ts:266–323 ↗verbatim · typescript · 0c0afa45
const makePkceRequest = Effect.gen(function* () {
  const crypto = yield* Crypto.Crypto;
  const verifier = Encoding.encodeBase64Url(yield* crypto.randomBytes(32));
  const challenge = Encoding.encodeBase64Url(
    yield* crypto.digest("SHA-256", new TextEncoder().encode(verifier)),
  );
  const state = Encoding.encodeBase64Url(yield* crypto.randomBytes(16));
  return { verifier, challenge, state };
});
 
export interface OutOfBandOAuthPromptInput {
  readonly authorizeUrl: string;
  readonly validate: (value: string) => Effect.Effect<string, string>;
}
 
/**
 * Out-of-band OAuth for machines without a local browser (SSH). The user
 * opens the hosted /connect URL elsewhere, signs in, and enters the displayed
 * code in this terminal. The PKCE verifier never leaves this process, so the
 * authorization code is useless to an observer, and the state bundled into
 * the blob preserves the loopback flow's CSRF check.
 */
export const outOfBandOAuthLogin = Effect.fn("cloud.cli_token.out_of_band_oauth_login")(function* <
  E,
  R,
>(promptForCode: (input: OutOfBandOAuthPromptInput) => Effect.Effect<string, E, R>) {
  const metadata = yield* cloudCliOAuthConfig;
  const hostedAppUrl = yield* hostedAppUrlConfig;
  const { verifier, challenge, state } = yield* makePkceRequest;
 
  const authorizationCode = yield* promptForCode({
    authorizeUrl: buildConnectAuthorizeRequestUrl({ hostedAppUrl, state, challenge }),
    validate: (value) => {
      const checked = checkConnectAuthCode(value, state);
      return typeof checked === "string" ? Effect.fail(checked) : Effect.succeed(value);
    },
  }).pipe(
    // Clerk authorization codes expire on this horizon anyway; matching the
    // loopback flow's timeout turns an abandoned prompt into a clear error.
    Effect.timeout(CLOUD_CLI_OAUTH_CALLBACK_TIMEOUT),
    Effect.catchTag("TimeoutError", (cause) =>
      Effect.fail(new CloudCliAuthorizationTimeoutError({ cause })),
    ),
  );
  // promptForCode is caller-supplied, so re-check the returned value rather
  // than trusting that the prompt ran validate.
  const authCode = checkConnectAuthCode(authorizationCode, state);
  if (typeof authCode === "string") {
    return yield* new CloudCliAuthorizationError({ cause: authCode });
  }
 
  return yield* exchangeToken(metadata, {
    grant_type: "authorization_code",
    code: authCode.code,
    redirect_uri: connectCallbackUrl(hostedAppUrl),
    client_id: metadata.clientId,
    code_verifier: verifier,
  });
Read this as: The out-of-band CLI flow keeps the verifier in-process, validates the returned state, and sends the authorization-code exchange to the configured token endpoint.

2. The device key binds proof, rather than becoming an account password

For managed relay access, a browser creates a P-256 signing key and retains it in its DPoP key store. Its public JWK yields a thumbprint. Each proof is a signed JWT with method (htm), normalized target URL (htu), fresh id (jti), issue time, and, when an access token is being presented, its hash (ath). The private key does not travel to the relay or environment.

The verifier checks the signature, requested method and URL, expected thumbprint, optional access-token hash, and a bounded issue-time window. The environment also persists a thumbprint-plus-jti replay marker. A captured access token therefore is not sufficient for a DPoP route without a matching fresh proof; a reused proof is rejected.

apps/web/src/cloud/dpop.ts:24–143 ↗verbatim · typescript · 6a8194ca
const DPOP_DATABASE_NAME = "t3code:cloud-auth";
const DPOP_DATABASE_VERSION = 1;
const DPOP_KEY_STORE_NAME = "keys";
const DPOP_KEY_ID = "relay-dpop-proof-key";
const decodeDpopPublicJwk = Schema.decodeUnknownEffect(DpopPublicJwk);
 
export const browserCryptoLayer = Layer.succeed(
  Crypto.Crypto,
  Crypto.make({
    randomBytes: (size) => globalThis.crypto.getRandomValues(new Uint8Array(size)),
    digest: (algorithm, data) =>
      Effect.promise(async () => {
        const input = new Uint8Array(data.length);
        input.set(data);
        return new Uint8Array(await globalThis.crypto.subtle.digest(algorithm, input.buffer));
      }),
  }),
);
 
function dpopError(message: string, cause?: unknown) {
  return new BrowserDpopError({ message, ...(cause === undefined ? {} : { cause }) });
}
 
function openDpopDatabase(): Effect.Effect<IDBDatabase, BrowserDpopError> {
  return Effect.callback<IDBDatabase, BrowserDpopError>((resume) => {
    const request = indexedDB.open(DPOP_DATABASE_NAME, DPOP_DATABASE_VERSION);
    request.addEventListener("error", () =>
      resume(
        Effect.fail(dpopError("Could not open DPoP key storage.", request.error ?? undefined)),
      ),
    );
    request.addEventListener("upgradeneeded", () => {
      if (!request.result.objectStoreNames.contains(DPOP_KEY_STORE_NAME)) {
        request.result.createObjectStore(DPOP_KEY_STORE_NAME);
      }
    });
    request.addEventListener("success", () => resume(Effect.succeed(request.result)));
  });
}
 
export function readStoredBrowserDpopKey(): Effect.Effect<BrowserDpopKey | null, BrowserDpopError> {
  if (typeof indexedDB === "undefined") {
    return Effect.succeed(null);
  }
  return Effect.acquireUseRelease(
    openDpopDatabase(),
    (database) =>
      Effect.callback<BrowserDpopKey | null, BrowserDpopError>((resume) => {
        const request = database
          .transaction(DPOP_KEY_STORE_NAME, "readonly")
          .objectStore(DPOP_KEY_STORE_NAME)
          .get(DPOP_KEY_ID);
        request.addEventListener("error", () =>
          resume(Effect.fail(dpopError("Could not read DPoP key.", request.error ?? undefined))),
        );
        request.addEventListener("success", () =>
          resume(Effect.succeed((request.result as BrowserDpopKey | undefined) ?? null)),
        );
      }),
    (database) => Effect.sync(() => database.close()),
  );
}
 
export function writeStoredBrowserDpopKey(
  key: BrowserDpopKey,
): Effect.Effect<void, BrowserDpopError> {
  if (typeof indexedDB === "undefined") {
    return Effect.void;
  }
  return Effect.acquireUseRelease(
    openDpopDatabase(),
    (database) =>
      Effect.callback<void, BrowserDpopError>((resume) => {
        const transaction = database.transaction(DPOP_KEY_STORE_NAME, "readwrite");
        transaction.addEventListener("error", () =>
          resume(
            Effect.fail(dpopError("Could not write DPoP key.", transaction.error ?? undefined)),
          ),
        );
        transaction.addEventListener("complete", () => resume(Effect.void));
        transaction.objectStore(DPOP_KEY_STORE_NAME).put(key, DPOP_KEY_ID);
      }),
    (database) => Effect.sync(() => database.close()),
  );
}
 
export const generateBrowserDpopKey = Effect.gen(function* () {
  const generated = yield* Effect.tryPromise({
    try: () =>
      crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, [
        "sign",
        "verify",
      ]) as Promise<CryptoKeyPair>,
    catch: (cause) => dpopError("Could not generate DPoP proof key.", cause),
  });
  const privateJwk = yield* Effect.tryPromise({
    try: () => crypto.subtle.exportKey("jwk", generated.privateKey),
    catch: (cause) => dpopError("Could not export DPoP private key.", cause),
  });
  const publicJwk = yield* Effect.tryPromise({
    try: () => crypto.subtle.exportKey("jwk", generated.publicKey),
    catch: (cause) => dpopError("Could not export DPoP public key.", cause),
  }).pipe(
    Effect.flatMap((jwk) => decodeDpopPublicJwk(jwk)),
    Effect.mapError((cause) =>
      cause instanceof BrowserDpopError
        ? cause
        : dpopError("Generated DPoP public key is invalid.", cause),
    ),
  );
  const privateKey = yield* Effect.tryPromise({
    try: () => importJWK(privateJwk as JWK, "ES256", { extractable: false }) as Promise<CryptoKey>,
    catch: (cause) => dpopError("Could not import DPoP private key.", cause),
  });
  return {
    privateKey,
    publicJwk,
    thumbprint: computeDpopJwkThumbprint(publicJwk),
  };
});
Read this as: The web implementation names a dedicated cloud-auth key store and generates a P-256 signing key whose thumbprint is derived from its public JWK.
packages/shared/src/dpop.ts:1–175 ↗verbatim · typescript · a201cbe8
import { p256 } from "@noble/curves/nist";
import { sha256 } from "@noble/hashes/sha2";
import * as Encoding from "effect/Encoding";
import * as Option from "effect/Option";
import * as Result from "effect/Result";
import * as Schema from "effect/Schema";
 
import { DpopPublicJwk as DpopPublicJwkSchema, normalizeDpopHtu } from "./dpopCommon.ts";
import type { DpopPublicJwk as DpopPublicJwkType } from "./dpopCommon.ts";
import { stableStringify } from "./relaySigning.ts";
 
const DPOP_TYP = "dpop+jwt";
const DPOP_ALG = "ES256";
const DEFAULT_MAX_AGE_SECONDS = 300;
 
export const DpopPublicJwk = DpopPublicJwkSchema;
export type DpopPublicJwk = DpopPublicJwkType;
export { normalizeDpopHtu };
 
const DpopJwtHeaderPublicJwk = Schema.Struct({
  ...DpopPublicJwkSchema.fields,
  d: Schema.optionalKey(Schema.Never),
});
 
const DpopJwtHeaderJson = Schema.fromJsonString(
  Schema.Struct({
    typ: Schema.Literal(DPOP_TYP),
    alg: Schema.Literal(DPOP_ALG),
    jwk: DpopJwtHeaderPublicJwk,
  }),
);
const decodeDpopJwtHeaderJson = Schema.decodeUnknownOption(DpopJwtHeaderJson);
 
const DpopJwtPayloadJson = Schema.fromJsonString(
  Schema.Struct({
    htm: Schema.String.check(Schema.isNonEmpty()),
    htu: Schema.String.check(Schema.isNonEmpty()),
    jti: Schema.String.check(Schema.isNonEmpty()),
    iat: Schema.Int,
    ath: Schema.optionalKey(Schema.String),
  }),
);
const decodeDpopJwtPayloadJson = Schema.decodeUnknownOption(DpopJwtPayloadJson);
 
export type DpopVerificationResult =
  | {
      readonly ok: true;
      readonly thumbprint: string;
      readonly jti: string;
      readonly iat: number;
    }
  | {
      readonly ok: false;
      readonly reason: string;
    };
 
function base64UrlToBytes(value: string): Uint8Array {
  return Result.getOrThrow(Encoding.decodeBase64Url(value));
}
 
function decodeBase64UrlDpopJwtHeader(value: string) {
  return decodeDpopJwtHeaderJson(Result.getOrThrow(Encoding.decodeBase64UrlString(value)));
}
 
function decodeBase64UrlDpopJwtPayload(value: string) {
  return decodeDpopJwtPayloadJson(Result.getOrThrow(Encoding.decodeBase64UrlString(value)));
}
 
function dpopThumbprintInput(jwk: DpopPublicJwkType): string {
  return stableStringify({
    crv: jwk.crv,
    kty: jwk.kty,
    x: jwk.x,
    y: jwk.y,
  });
}
 
export function computeDpopJwkThumbprint(jwk: DpopPublicJwkType): string {
  return Encoding.encodeBase64Url(sha256(new TextEncoder().encode(dpopThumbprintInput(jwk))));
}
 
export function computeDpopAccessTokenHash(accessToken: string): string {
  return Encoding.encodeBase64Url(sha256(new TextEncoder().encode(accessToken)));
}
 
function publicKeyBytesFromJwk(jwk: DpopPublicJwkType): Uint8Array {
  const x = base64UrlToBytes(jwk.x);
  const y = base64UrlToBytes(jwk.y);
  if (x.length !== 32 || y.length !== 32) {
    throw new Error("Invalid P-256 public key coordinate length.");
  }
  const publicKey = new Uint8Array(65);
  publicKey[0] = 0x04;
  publicKey.set(x, 1);
  publicKey.set(y, 33);
  return publicKey;
}
 
export function verifyDpopProof(input: {
  readonly proof: string | null | undefined;
  readonly method: string;
  readonly url: string;
  readonly nowEpochSeconds: number;
  readonly expectedThumbprint?: string;
  readonly expectedAccessToken?: string;
  readonly maxAgeSeconds?: number;
}): DpopVerificationResult {
  if (!input.proof?.trim()) {
    return { ok: false, reason: "Missing DPoP proof." };
  }
 
  const parts = input.proof.split(".");
  if (parts.length !== 3 || !parts[0] || !parts[1] || !parts[2]) {
    return { ok: false, reason: "Invalid DPoP compact JWT." };
  }
 
  try {
    const header = decodeBase64UrlDpopJwtHeader(parts[0]);
    const payload = decodeBase64UrlDpopJwtPayload(parts[1]);
    if (Option.isNone(header)) {
      return { ok: false, reason: "Invalid DPoP JWT header." };
    }
    if (Option.isNone(payload)) {
      return { ok: false, reason: "Invalid DPoP JWT payload." };
    }
 
    const thumbprint = computeDpopJwkThumbprint(header.value.jwk);
    if (input.expectedThumbprint && thumbprint !== input.expectedThumbprint) {
      return { ok: false, reason: "DPoP key thumbprint mismatch." };
    }
    if (payload.value.htm.toUpperCase() !== input.method.toUpperCase()) {
      return { ok: false, reason: "DPoP method mismatch." };
    }
    const normalizedHtu = normalizeDpopHtu(input.url);
    if (normalizedHtu === null || payload.value.htu !== normalizedHtu) {
      return { ok: false, reason: "DPoP URL mismatch." };
    }
    if (input.expectedAccessToken) {
      const expectedAth = computeDpopAccessTokenHash(input.expectedAccessToken);
      if (payload.value.ath !== expectedAth) {
        return { ok: false, reason: "DPoP access token hash mismatch." };
      }
    }
 
    const maxAgeSeconds = input.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS;
    if (
      payload.value.iat > input.nowEpochSeconds + 5 ||
      input.nowEpochSeconds - payload.value.iat > maxAgeSeconds
    ) {
      return { ok: false, reason: "DPoP proof is outside the allowed time window." };
    }
 
    const signature = base64UrlToBytes(parts[2]);
    const signatureInputHash = sha256(new TextEncoder().encode(`${parts[0]}.${parts[1]}`));
    const verified = p256.verify(
      signature,
      signatureInputHash,
      publicKeyBytesFromJwk(header.value.jwk),
      {
        prehash: false,
        format: "compact",
      },
    );
    return verified
      ? {
          ok: true,
          thumbprint,
          jti: payload.value.jti,
          iat: payload.value.iat,
        }
      : { ok: false, reason: "Invalid DPoP signature." };
  } catch {
    return { ok: false, reason: "Invalid DPoP proof." };
  }
}
Read this as: Shared verification checks proof structure, thumbprint, method, URL, optional token hash, freshness, and ES256 signature.

3. Linking registers an environment and its managed endpoint intent

Linking has two authorities. First the signed-in client asks the relay for an environment-link challenge. It then asks the local primary environment to sign a link proof containing its descriptor, environment public key, requested endpoint, origin, scopes, and challenge. The relay verifies the proof and challenge, consumes replay nonces, checks that the descriptor and environment id agree, and upserts the account-to-environment link.

For a managed link, the relay accepts only a loopback origin, provisions a managed endpoint, then returns both a per-environment relay credential and runtime connector configuration. The local environment persists that configuration and starts the managed connector. --publish-only deliberately takes a different path: it links activity publishing but asks for no managed tunnel, so a client must reach the environment out of band.

apps/web/src/cloud/linkEnvironment.ts:402–493 ↗verbatim · typescript · 205bb065
export function linkPrimaryEnvironmentToCloud(input: {
  readonly target: CloudLinkTarget;
  readonly clerkToken: string;
  readonly mode?: CloudLinkMode;
}): Effect.Effect<
  void,
  CloudEnvironmentLinkError,
  EnvironmentRegistry | HttpClient.HttpClient | ManagedRelay.ManagedRelayClient
> {
  return Effect.gen(function* () {
    const configuredRelayUrl = relayUrl();
    if (!configuredRelayUrl) {
      return yield* new CloudEnvironmentLinkError({
        message: "T3CODE_RELAY_URL is not configured.",
      });
    }
    const managedTunnelsEnabled = (input.mode ?? "managed") === "managed";
    const providerKind = managedTunnelsEnabled
      ? MANAGED_ENDPOINT_PROVIDER_KIND
      : PUBLISH_ONLY_PROVIDER_KIND;
    const relayClient = yield* ManagedRelay.ManagedRelayClient;
    const environmentClient = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl);
    if (managedTunnelsEnabled) {
      yield* ensureRelayClientAvailable(EnvironmentId.make(input.target.environmentId));
    }
 
    const challenge = yield* relayClient
      .createEnvironmentLinkChallenge({
        clerkToken: input.clerkToken,
        payload: {
          notificationsEnabled: true,
          liveActivitiesEnabled: true,
          managedTunnelsEnabled,
        },
      })
      .pipe(
        Effect.mapError(
          decodedRelayClientError(
            `${configuredRelayUrl}/v1/client/environment-link-challenges failed`,
          ),
        ),
      );
    const proof = yield* environmentClient.connect
      .linkProof({
        headers: {},
        payload: {
          challenge: challenge.challenge,
          relayIssuer: configuredRelayUrl,
          endpoint: {
            httpBaseUrl: input.target.httpBaseUrl,
            wsBaseUrl: input.target.wsBaseUrl,
            providerKind,
          },
          origin: endpointOrigin(input.target.httpBaseUrl),
        },
      })
      .pipe(Effect.mapError(environmentApiError("Could not obtain environment link proof.")));
    const link = yield* relayClient
      .linkEnvironment({
        clerkToken: input.clerkToken,
        payload: {
          proof,
          notificationsEnabled: true,
          liveActivitiesEnabled: true,
          managedTunnelsEnabled,
        },
      })
      .pipe(
        Effect.mapError(
          decodedRelayClientError(`${configuredRelayUrl}/v1/client/environment-links failed`),
        ),
      );
    yield* ensureLinkedEnvironmentMatches({
      expectedEnvironmentId: input.target.environmentId,
      expectedProviderKind: providerKind,
      link,
    });
 
    yield* environmentClient.connect
      .relayConfig({
        headers: {},
        payload: {
          relayUrl: configuredRelayUrl,
          relayIssuer: link.relayIssuer,
          cloudUserId: link.cloudUserId,
          environmentCredential: link.environmentCredential,
          cloudMintPublicKey: link.cloudMintPublicKey,
          endpointRuntime: link.endpointRuntime,
        },
      })
      .pipe(Effect.mapError(environmentApiError("Could not configure environment relay access.")));
  }).pipe(Effect.provide(primaryEnvironmentHttpLayer));
Read this as: The web link flow obtains a relay challenge, has the environment create the proof, submits it to the relay, then supplies returned credential and endpoint-runtime configuration to the environment.
infra/relay/src/environments/EnvironmentLinker.ts:227–341 ↗verbatim · typescript · 34db35e5
      const challenge = yield* relayTokens.verifyLinkChallenge({
        token: verified.challenge,
        userId: input.userId,
        request: {
          notificationsEnabled: input.request.notificationsEnabled,
          liveActivitiesEnabled: input.request.liveActivitiesEnabled,
          managedTunnelsEnabled: input.request.managedTunnelsEnabled,
        },
        nowEpochSeconds: nowSeconds,
      });
      if (challenge === null) {
        return yield* new EnvironmentLinkProofInvalid({
          userId: input.userId,
          environmentId: verified.environmentId,
          reason: "challenge_invalid",
          stage: "verify_challenge",
        });
      }
      const expiresAt = DateTime.make(verified.exp * 1_000);
      if (expiresAt._tag === "None") {
        return yield* new EnvironmentLinkProofInvalid({
          userId: input.userId,
          environmentId: verified.environmentId,
          reason: "invalid_signature_or_scope",
          stage: "validate_expiration",
        });
      }
      const consumedNonce = yield* proofReplay.consume({
        thumbprint: verified.environmentPublicKey,
        jti: verified.jti,
        iat: verified.iat,
        expiresAt: expiresAt.value,
      });
      if (!consumedNonce) {
        return yield* new EnvironmentLinkProofInvalid({
          userId: input.userId,
          environmentId: verified.environmentId,
          reason: "replayed_nonce",
          stage: "consume_proof_nonce",
        });
      }
      const consumedChallenge = yield* proofReplay.consume({
        thumbprint: "relay-environment-link-challenge",
        jti: challenge.jti,
        iat: challenge.iat,
        expiresAt: expiresAt.value,
      });
      if (!consumedChallenge) {
        return yield* new EnvironmentLinkProofInvalid({
          userId: input.userId,
          environmentId: verified.environmentId,
          reason: "challenge_invalid",
          stage: "consume_challenge_nonce",
        });
      }
      if (input.request.managedTunnelsEnabled && !isLoopbackManagedTunnelOrigin(verified.origin)) {
        return yield* new EnvironmentLinkProofInvalid({
          userId: input.userId,
          environmentId: verified.environmentId,
          reason: "origin_not_allowed",
          stage: "validate_origin",
        });
      }
      // Downgrading a managed link to publish-only must release the tunnel and
      // DNS that were provisioned for it — nothing else cleans them up until a
      // full unlink. Best effort: a cleanup failure must not block the link
      // itself, and the provider treats an absent allocation as already
      // deprovisioned, so retrying on every non-tunnel link is cheap.
      if (!input.request.managedTunnelsEnabled) {
        yield* managedEndpointProvider
          .deprovision({
            userId: input.userId,
            environmentId: verified.environmentId,
          })
          .pipe(
            Effect.tapError((error) =>
              Effect.logWarning("managed endpoint deprovision on publish-only link failed", {
                environmentId: verified.environmentId,
                errorTag: error._tag,
              }),
            ),
            Effect.ignore,
          );
      }
      const provisioned = input.request.managedTunnelsEnabled
        ? yield* managedEndpointProvider.provision({
            userId: input.userId,
            environmentId: verified.environmentId,
            origin: verified.origin,
          })
        : null;
      const endpoint = provisioned?.endpoint ?? verified.endpoint;
      // The secure-endpoint requirement only matters when the relay advertises
      // this endpoint for other devices to reach (managed tunnel). Publish-only
      // links are reached out of band (e.g. Tailscale) and their stored endpoint
      // is never used for routing, so a nominal endpoint is acceptable.
      if (input.request.managedTunnelsEnabled && !isSecureManagedEndpoint(endpoint)) {
        return yield* new EnvironmentLinkProofInvalid({
          userId: input.userId,
          environmentId: verified.environmentId,
          reason: "endpoint_not_secure",
          stage: "validate_endpoint",
        });
      }
      yield* links.upsert({ ...input, proof: verified, endpoint });
      const environmentCredential = yield* credentials.create({
        environmentId: verified.environmentId,
        environmentPublicKey: verified.environmentPublicKey,
      });
      return {
        environmentId: verified.environmentId,
        endpoint,
        endpointRuntime: provisioned?.runtime ?? null,
        environmentCredential,
      };
Read this as: The relay verifies challenge and replay state, requires loopback for a managed tunnel, provisions it, records the link, and creates the environment credential.

4. A tunnel exposes the environment; it does not route through the relay

Provisioning derives a stable hostname and tunnel name, reserves an allocation, creates or reuses the Cloudflare tunnel, points its ingress at the local loopback HTTP origin, creates a proxied CNAME to *.cfargotunnel.com, obtains a connector token, and marks the allocation ready. The environment runtime launches the managed client as tunnel run with that token and supervises it.

That is an endpoint-exposure path: client traffic can travel through the managed Cloudflare tunnel to the environment. It is not a relay data hop. The relay’s own README says that normal API and WebSocket traffic goes directly between client and selected environment after connection; the client runtime independently constructs the environment’s token and WebSocket-ticket requests from the endpoint URL.

Figure 35.1 · T3 Connect launches a direct environment sessionsolid arrows are observed request paths; the dashed boundary separates relay control work from normal T3 traffic
T3 Connect control plane and direct data planeDiagram loading

Zoom with the controls, +/, or Ctrl/ + trackpad scroll. Enable Pan to drag, use two-finger scrolling, or use the arrow keys. 0 fits the diagram; Esc leaves Pan or expanded view.

T3 Connect control plane and direct data plane
Text equivalent

Control-plane paths are client to Clerk, client to relay, and relay to environment while linking or minting. The environment’s cloudflared connector reaches the Cloudflare tunnel service. After launch, client-to-environment HTTP and WebSocket traffic uses the managed endpoint directly. The relay is not on that normal traffic path.

Figure 35.1. A signed-in client uses Clerk and a device DPoP key to call the relay for discovery, link, or connection authorization. Link setup gives the environment connector configuration for a Cloudflare tunnel. To connect, the relay asks the managed environment to mint a bootstrap credential bound to the device key. The client then directly exchanges that bootstrap at the managed environment for a DPoP access token and directly obtains a WebSocket ticket. Ordinary API and WebSocket traffic runs between client and environment through its endpoint; it does not traverse the relay.
infra/relay/src/environments/ManagedEndpointProvider.ts:604–855 ↗verbatim · typescript · d9dfb0d6
    provision: Effect.fn("relay.managed_endpoint_provider.provision")(function* (input) {
      yield* Effect.annotateCurrentSpan({
        "relay.user_id": input.userId,
        "relay.environment_id": input.environmentId,
        "relay.managed_endpoint.origin_host": input.origin.localHttpHost,
        "relay.managed_endpoint.origin_port": input.origin.localHttpPort,
      });
      if (!isLoopbackOrigin(input.origin)) {
        return yield* new ManagedEndpointOriginNotAllowed({
          userId: input.userId,
          environmentId: input.environmentId,
          host: input.origin.localHttpHost,
          port: input.origin.localHttpPort,
        });
      }
      const cf = yield* requireCloudflareSettings(config, input);
      const environmentHash = yield* crypto
        .digest(
          "SHA-256",
          new TextEncoder().encode(
            managedEndpointDigestInput(cf.namespace, input.userId, input.environmentId),
          ),
        )
        .pipe(
          Effect.map(Encoding.encodeHex),
          Effect.mapError(
            (cause) =>
              new ManagedEndpointProvisioningFailed({
                userId: input.userId,
                environmentId: input.environmentId,
                stage: "derive-environment-hash",
                cause,
              }),
          ),
        );
      const requestedHostname = managedEndpointHostname(
        cf.namespace,
        cf.baseDomain,
        environmentHash,
      );
      const requestedTunnelName = managedEndpointTunnelName(cf.namespace, environmentHash);
      yield* tunnelLimits
        .ensureCapacity({
          userId: input.userId,
          environmentId: input.environmentId,
        })
        .pipe(
          Effect.catchTags({
            ManagedTunnelLimitPersistenceError: (cause) =>
              Effect.fail(
                new ManagedEndpointProvisioningFailed({
                  userId: input.userId,
                  environmentId: input.environmentId,
                  stage: "check-tunnel-limit",
                  hostname: requestedHostname,
                  tunnelName: requestedTunnelName,
                  cause,
                }),
              ),
          }),
        );
      const allocation = yield* allocations
        .reserve({
          userId: input.userId,
          environmentId: input.environmentId,
          hostname: requestedHostname,
          tunnelName: requestedTunnelName,
        })
        .pipe(
          Effect.mapError(
            (cause) =>
              new ManagedEndpointProvisioningFailed({
                userId: input.userId,
                environmentId: input.environmentId,
                stage: "reserve-allocation",
                hostname: requestedHostname,
                tunnelName: requestedTunnelName,
                cause,
              }),
          ),
        );
      const { hostname, tunnelName } = allocation;
 
      const tunnelResponse = yield* tunnels.list({ name: tunnelName, isDeleted: false }).pipe(
        Effect.map((tunnels) => tunnels.result),
        Effect.map(Arr.findFirst((tunnel) => tunnel.name === tunnelName)),
        Effect.flatMap(
          Option.match({
            onSome: (tunnel) => Effect.succeed(tunnel),
            onNone: () => tunnels.create({ name: tunnelName, configSrc: "cloudflare" }),
          }),
        ),
        Effect.mapError(
          (cause) =>
            new ManagedEndpointProvisioningFailed({
              userId: input.userId,
              environmentId: input.environmentId,
              stage: "ensure-tunnel",
              hostname,
              tunnelName,
              cause,
            }),
        ),
      );
      if (!tunnelResponse.id || tunnelResponse.name !== tunnelName) {
        return yield* new ManagedEndpointProvisioningFailed({
          userId: input.userId,
          environmentId: input.environmentId,
          stage: "validate-tunnel-response",
          hostname,
          tunnelName,
          ...(tunnelResponse.id ? { returnedTunnelId: tunnelResponse.id } : {}),
          ...(tunnelResponse.name ? { returnedTunnelName: tunnelResponse.name } : {}),
        });
      }
      const tunnel = { id: tunnelResponse.id, name: tunnelResponse.name };
      yield* allocations
        .recordTunnel({
          userId: input.userId,
          environmentId: input.environmentId,
          tunnelId: tunnel.id,
        })
        .pipe(
          Effect.mapError(
            (cause) =>
              new ManagedEndpointProvisioningFailed({
                userId: input.userId,
                environmentId: input.environmentId,
                stage: "record-tunnel",
                hostname,
                tunnelName,
                tunnelId: tunnel.id,
                cause,
              }),
          ),
        );
 
      yield* tunnels
        .putConfiguration(tunnel.id, {
          ingress: [
            {
              hostname,
              service: formatOriginService(input.origin),
            },
            { service: "http_status:404" },
          ],
        })
        .pipe(
          Effect.mapError(
            (cause) =>
              new ManagedEndpointProvisioningFailed({
                userId: input.userId,
                environmentId: input.environmentId,
                stage: "configure-tunnel",
                hostname,
                tunnelName,
                tunnelId: tunnel.id,
                cause,
              }),
          ),
        );
 
      const dnsRecord = {
        type: "CNAME",
        name: hostname,
        content: `${tunnel.id}.cfargotunnel.com`,
        ttl: 1,
        proxied: true,
      } as const;
 
      const dnsRecordId = yield* ensureDnsRecord(hostname, allocation.dnsRecordId, dnsRecord).pipe(
        Effect.mapError(
          (cause) =>
            new ManagedEndpointProvisioningFailed({
              userId: input.userId,
              environmentId: input.environmentId,
              stage: "ensure-dns-record",
              hostname,
              tunnelName,
              tunnelId: tunnel.id,
              ...(allocation.dnsRecordId === null ? {} : { dnsRecordId: allocation.dnsRecordId }),
              cause,
            }),
        ),
      );
      yield* allocations
        .recordDns({
          userId: input.userId,
          environmentId: input.environmentId,
          dnsRecordId,
        })
        .pipe(
          Effect.mapError(
            (cause) =>
              new ManagedEndpointProvisioningFailed({
                userId: input.userId,
                environmentId: input.environmentId,
                stage: "record-dns",
                hostname,
                tunnelName,
                tunnelId: tunnel.id,
                dnsRecordId,
                cause,
              }),
          ),
        );
 
      const connectorToken = yield* tunnels.getToken(tunnel.id).pipe(
        Effect.mapError(
          (cause) =>
            new ManagedEndpointProvisioningFailed({
              userId: input.userId,
              environmentId: input.environmentId,
              stage: "get-tunnel-token",
              hostname,
              tunnelName,
              tunnelId: tunnel.id,
              dnsRecordId,
              cause,
            }),
        ),
      );
      yield* allocations
        .markReady({
          userId: input.userId,
          environmentId: input.environmentId,
        })
        .pipe(
          Effect.mapError(
            (cause) =>
              new ManagedEndpointProvisioningFailed({
                userId: input.userId,
                environmentId: input.environmentId,
                stage: "mark-allocation-ready",
                hostname,
                tunnelName,
                tunnelId: tunnel.id,
                dnsRecordId,
                cause,
              }),
          ),
        );
 
      return {
        endpoint: managedEndpointForHostname(hostname),
        runtime: {
          providerKind: "cloudflare_tunnel",
          connectorToken,
          tunnelId: tunnel.id,
          tunnelName: tunnel.name,
        },
      } satisfies ManagedEndpointProvisioningResult;
Read this as: The provider derives an allocation, configures tunnel ingress to the loopback origin, records DNS, receives a connector token, and returns cloudflare_tunnel runtime configuration.
apps/server/src/cloud/ManagedEndpointRuntime.ts:187–287 ↗verbatim · typescript · 15a5736f
  reconcileConfig = Effect.fn("CloudManagedEndpointRuntime.reconcileConfig")(function* (config) {
    if (!config || config.providerKind !== "cloudflare_tunnel") {
      yield* stopActive;
      return config
        ? { status: "unsupported", providerKind: config.providerKind }
        : { status: "disabled" };
    }
 
    const nextConfigKey = runtimeConfigKey(config);
    const active = yield* Ref.get(activeRef);
    if (active?.configKey === nextConfigKey) {
      const isRunning = yield* active.child.isRunning.pipe(Effect.orElseSucceed(() => false));
      if (isRunning) {
        return {
          status: "running",
          providerKind: "cloudflare_tunnel",
          pid: Number(active.child.pid),
          ...(active.config.tunnelId ? { tunnelId: active.config.tunnelId } : {}),
          ...(active.config.tunnelName ? { tunnelName: active.config.tunnelName } : {}),
        } satisfies CloudManagedEndpointRuntimeStatus;
      }
    }
 
    yield* stopActive;
 
    const executable = yield* relayClient.resolve;
    if (executable.status !== "available") {
      return {
        status: "failed",
        providerKind: "cloudflare_tunnel",
        reason:
          executable.status === "unsupported"
            ? `Relay client is unsupported on ${executable.platform}-${executable.arch}.`
            : "The relay client is not installed.",
        ...(config.tunnelId ? { tunnelId: config.tunnelId } : {}),
        ...(config.tunnelName ? { tunnelName: config.tunnelName } : {}),
      } satisfies CloudManagedEndpointRuntimeStatus;
    }
 
    const connectorScope = yield* Scope.make("sequential");
    const child = yield* spawner
      .spawn(
        ChildProcess.make(executable.executablePath, ["tunnel", "run"], {
          detached: false,
          env: {
            ...process.env,
            TUNNEL_TOKEN: config.connectorToken,
          },
          shell: false,
          stderr: "pipe",
          stdout: "pipe",
        }),
      )
      .pipe(
        Effect.provideService(Scope.Scope, connectorScope),
        Effect.tap((child) =>
          Effect.logInfo("Relay client process started; waiting for tunnel connection", {
            pid: Number(child.pid),
            tunnelId: config.tunnelId,
            tunnelName: config.tunnelName,
          }),
        ),
        Effect.catch((cause) =>
          Effect.logWarning("Failed to start relay client", {
            cause,
            tunnelId: config.tunnelId,
            tunnelName: config.tunnelName,
          }).pipe(
            Effect.andThen(Scope.close(connectorScope, Exit.void).pipe(Effect.ignore)),
            Effect.as({
              status: "failed",
              providerKind: "cloudflare_tunnel",
              reason: String(cause),
              ...(config.tunnelId ? { tunnelId: config.tunnelId } : {}),
              ...(config.tunnelName ? { tunnelName: config.tunnelName } : {}),
            } satisfies CloudManagedEndpointRuntimeStatus),
          ),
        ),
      );
 
    if ("status" in child && child.status === "failed") {
      return child;
    }
 
    if (!("status" in child)) {
      const connector = {
        child,
        scope: connectorScope,
        configKey: nextConfigKey,
        config,
      } satisfies ActiveConnector;
      yield* Ref.set(activeRef, connector);
      yield* Effect.forkIn(observeConnectorOutput(connector), connectorScope);
      yield* Effect.forkIn(superviseConnector(connector), connectorScope);
      return {
        status: "running",
        providerKind: "cloudflare_tunnel",
        pid: Number(child.pid),
        ...(config.tunnelId ? { tunnelId: config.tunnelId } : {}),
        ...(config.tunnelName ? { tunnelName: config.tunnelName } : {}),
      } satisfies CloudManagedEndpointRuntimeStatus;
Read this as: The environment starts the managed relay client as tunnel run with the connector token and supervises that child as its own runtime concern.

5. The exact credential ladder crosses the relay once, then leaves it

The verbs matter more than calling every string a “token.” A fresh connection takes this ladder:

  1. A Clerk session credential authorizes the account with the relay.
  2. The client’s DPoP key signs a proof; the relay exchanges the Clerk credential for a scoped, short-lived relay DPoP access token and requires proof of the same key.
  3. The relay’s connect operation creates a two-minute signed mint request bound to the client key thumbprint and sends it to the managed environment. The verified response supplies an environment bootstrap credential.
  4. The client calls that environment’s /oauth/token directly with the bootstrap credential and a DPoP proof. The environment issues a DPoP-bound environment access token.
  5. The client calls that environment’s WebSocket-ticket endpoint directly with the environment token and another proof, then attaches the resulting ticket to the environment WebSocket URL.
  6. The client and environment carry ordinary T3 API/WebSocket work directly. The tunnel is the reachable endpoint; the relay is not the normal hot path.

The bootstrap credential is intentionally not the long-lived data-plane session, and the relay DPoP token is intentionally not an environment token. That separation is why a relay-side account operation, a device proof, an environment access session, and a ticket can have distinct expiry, audience, and replay rules.

infra/relay/src/environments/EnvironmentConnector.ts:541–670 ↗verbatim · typescript · 87e76875
    connect: Effect.fn("relay.environment_connector.connect")(function* (input) {
      yield* Effect.annotateCurrentSpan({
        "relay.environment_id": input.environmentId,
        "relay.operation": "connect",
        "relay.connect.has_device_id": input.deviceId !== undefined,
        ...(input.deviceId ? { "relay.mobile.device_id": input.deviceId } : {}),
      });
      if (input.clientProofKeyThumbprint.trim().length === 0) {
        return yield* new EnvironmentConnectNotAuthorized({
          environmentId: input.environmentId,
          operation: "connect",
          reason: "client_proof_key_thumbprint_missing",
        });
      }
      const { link, allocation } = yield* Effect.all(
        {
          link: links.getForUser(input),
          allocation: allocations.get(input),
        },
        { concurrency: 2 },
      );
      if (!link) {
        return yield* new EnvironmentConnectNotAuthorized({
          environmentId: input.environmentId,
          operation: "connect",
          reason: "environment_link_not_found",
        });
      }
      const endpoint = yield* resolveManagedEndpoint({
        operation: "connect",
        link,
        allocation,
      });
      const now = yield* DateTime.now;
      const expiresAt = DateTime.add(now, { minutes: 2 });
      const nonce = yield* crypto.randomUUIDv4.pipe(
        Effect.mapError(
          (cause) =>
            new EnvironmentMintRequestFailed({
              environmentId: input.environmentId,
              operation: "connect",
              cause,
            }),
        ),
      );
      const payload = {
        iss: relayIssuer,
        aud: `t3-env:${link.environmentId}`,
        sub: input.userId,
        jti: yield* crypto.randomUUIDv4.pipe(
          Effect.mapError(
            (cause) =>
              new EnvironmentMintRequestFailed({
                environmentId: input.environmentId,
                operation: "connect",
                cause,
              }),
          ),
        ),
        iat: Math.floor(now.epochMilliseconds / 1_000),
        exp: Math.floor(expiresAt.epochMilliseconds / 1_000),
        environmentId: link.environmentId,
        clientProofKeyThumbprint: input.clientProofKeyThumbprint,
        cnf: { jkt: input.clientProofKeyThumbprint },
        ...(input.deviceId ? { deviceId: input.deviceId } : {}),
        nonce,
        scope: ["environment:connect"],
      } satisfies RelayCloudMintCredentialProofPayload;
      const proof = yield* signRelayJwt({
        privateKey: Redacted.value(settings.cloudMintPrivateKey),
        typ: RELAY_MINT_REQUEST_TYP,
        payload,
      }).pipe(
        Effect.mapError(
          (cause) =>
            new EnvironmentMintRequestFailed({
              environmentId: input.environmentId,
              operation: "connect",
              cause,
            }),
        ),
      );
      const environmentClient = yield* makeEnvironmentClient(endpoint.httpBaseUrl);
      const decoded = yield* environmentClient.connect
        .t3MintCredential({ payload: { proof } })
        .pipe(
          withoutRedirects,
          Effect.mapError(
            (cause) =>
              new EnvironmentMintRequestFailed({
                environmentId: input.environmentId,
                operation: "connect",
                cause,
              }),
          ),
          Effect.timeoutOption(Duration.millis(ENVIRONMENT_MINT_REQUEST_TIMEOUT_MS)),
          Effect.flatMap(
            Option.match({
              onNone: () =>
                Effect.fail(
                  new EnvironmentMintRequestTimedOut({
                    environmentId: input.environmentId,
                    timeoutMs: ENVIRONMENT_MINT_REQUEST_TIMEOUT_MS,
                  }),
                ),
              onSome: Effect.succeed,
            }),
          ),
        );
      const verified = yield* verifyEnvironmentResponse({
        response: decoded,
        environmentId: input.environmentId,
        requestNonce: nonce,
        clientProofKeyThumbprint: input.clientProofKeyThumbprint,
        environmentPublicKeys: [link.environmentPublicKey],
        relayIssuer,
        nowEpochSeconds: Math.floor(now.epochMilliseconds / 1_000),
      });
      if (!verified) {
        return yield* new EnvironmentMintResponseInvalid({
          environmentId: input.environmentId,
          operation: "connect",
        });
      }
      return {
        environmentId: link.environmentId,
        endpoint,
        credential: decoded.credential,
        expiresAt: decoded.expiresAt,
      };
Read this as: The relay connect operation binds a short-lived mint request to the client proof-key thumbprint, asks the environment to mint, verifies the response, and returns a bootstrap credential.
packages/client-runtime/src/authorization/service.ts:180–299 ↗verbatim · typescript · 6815918a
  const authorizeDpop = Effect.fn("clientRuntime.connection.remote.authorizeDpop")(
    function* (input: {
      readonly expectedEnvironmentId: Parameters<
        RemoteEnvironmentAuthorization["Service"]["authorizeDpop"]
      >[0]["expectedEnvironmentId"];
      readonly obtainBootstrap: Parameters<
        RemoteEnvironmentAuthorization["Service"]["authorizeDpop"]
      >[0]["obtainBootstrap"];
    }) {
      const thumbprint = yield* signer.thumbprint.pipe(
        Effect.mapError(
          () =>
            new ConnectionBlockedError({
              reason: "configuration",
              detail: "Could not load the environment authorization key.",
            }),
        ),
        Effect.withSpan("environment.authorization.dpopKey.resolve"),
      );
      const now = yield* Clock.currentTimeMillis;
      const cached = yield* tokenStore
        .get(input.expectedEnvironmentId)
        .pipe(Effect.withSpan("environment.authorization.accessToken.cache"));
      if (
        Option.isSome(cached) &&
        cached.value.environmentId === input.expectedEnvironmentId &&
        cached.value.dpopThumbprint === thumbprint &&
        cached.value.expiresAtEpochMs > now + TOKEN_EXPIRY_SAFETY_MARGIN_MS
      ) {
        yield* Effect.annotateCurrentSpan({
          "connection.remote_token_cache": "hit",
        });
        const cachedSocket = yield* createDpopSocketUrl(
          cached.value,
          CACHED_ENDPOINT_SOCKET_TIMEOUT_MS,
        ).pipe(Effect.result);
        if (Result.isSuccess(cachedSocket)) {
          return {
            environmentId: cached.value.environmentId,
            label: cached.value.label,
            httpBaseUrl: cached.value.endpoint.httpBaseUrl,
            socketUrl: cachedSocket.success,
            httpAuthorization: {
              _tag: "Dpop" as const,
              accessToken: cached.value.accessToken,
            },
          };
        }
        if (cachedSocket.failure._tag === "ConnectionBlockedError") {
          return yield* mapDpopSocketError(cachedSocket.failure);
        }
        yield* tokenStore
          .remove(input.expectedEnvironmentId)
          .pipe(Effect.withSpan("environment.authorization.accessToken.remove"));
      }
 
      yield* Effect.annotateCurrentSpan({
        "connection.remote_token_cache": "miss",
      });
      const bootstrap = yield* input.obtainBootstrap;
      const descriptor = yield* fetchDescriptor(bootstrap.endpoint.httpBaseUrl).pipe(
        Effect.provideService(HttpClient.HttpClient, httpClient),
        Effect.withSpan("environment.authorization.descriptor"),
      );
      if (descriptor.environmentId !== input.expectedEnvironmentId) {
        return yield* environmentMismatchError({
          expected: input.expectedEnvironmentId,
          actual: descriptor.environmentId,
        });
      }
      const bootstrapProof = yield* signer
        .createProof({
          method: "POST",
          url: environmentEndpointUrl(bootstrap.endpoint.httpBaseUrl, "/oauth/token"),
        })
        .pipe(
          Effect.mapError(
            () =>
              new ConnectionBlockedError({
                reason: "configuration",
                detail: "Could not create the environment authorization proof.",
              }),
          ),
        );
      const access = yield* exchangeRemoteDpopAccessToken({
        httpBaseUrl: bootstrap.endpoint.httpBaseUrl,
        credential: bootstrap.credential,
        dpopProof: bootstrapProof,
        scopes: presentation.scopes,
        clientMetadata: presentation.metadata,
      }).pipe(
        Effect.mapError(mapRemoteEnvironmentError),
        Effect.provideService(HttpClient.HttpClient, httpClient),
        Effect.withSpan("environment.authorization.accessToken.exchange"),
      );
      const issuedAt = yield* Clock.currentTimeMillis;
      const token = new TokenStore.RemoteDpopAccessToken({
        environmentId: descriptor.environmentId,
        label: descriptor.label,
        endpoint: bootstrap.endpoint,
        accessToken: access.access_token,
        expiresAtEpochMs: issuedAt + access.expires_in * 1_000,
        dpopThumbprint: thumbprint,
      });
      const socketUrl = yield* createDpopSocketUrl(token).pipe(Effect.mapError(mapDpopSocketError));
      yield* tokenStore
        .put(token)
        .pipe(Effect.withSpan("environment.authorization.accessToken.persist"));
      return {
        environmentId: descriptor.environmentId,
        label: descriptor.label,
        httpBaseUrl: bootstrap.endpoint.httpBaseUrl,
        socketUrl,
        httpAuthorization: {
          _tag: "Dpop" as const,
          accessToken: token.accessToken,
        },
      };
    },
  );
Read this as: The client uses its DPoP signer to exchange the relay-provided bootstrap at the environment, persists the key-bound access token, and obtains a direct socket URL.
Interactive credential ladder · Chapter 35

Launch the connection, then move the work on a different path

Choose a manual step. The lab distinguishes the short setup exchange from the HTTP and WebSocket traffic that follows it.

Control plane · account and endpoint authorization

Clerk session reaches the relay

A signed-in client presents its Clerk credential to list or link environments. This authorizes relay control-plane work; it is not an environment WebSocket credential.

Sender → receiver
Client → relay
Proof in use
Clerk bearer credential
What becomes possible
Discovery, link challenge, or relay DPoP token exchange

Step 1 of 6. Control plane.

Static credential ladder and plane boundary
StepPathCredential or proofResult
1Client → relayClerk bearer credentialAccount-scoped relay operations
2Client → relayDevice P-256 DPoP proof; relay issues DPoP-bound access tokenProof-bound relay request authority
3Relay → managed environmentRelay-signed short-lived mint request, bound to the client key thumbprintEnvironment bootstrap credential returned through the brokered connection flow
4Client → managed environmentBootstrap credential plus DPoP proofEnvironment DPoP access token
5Client → managed environmentEnvironment DPoP access token plus fresh proofWebSocket ticket
6Client ↔ managed environmentTicketed WebSocket and direct API authorizationNormal T3 HTTP and WebSocket traffic; it does not traverse the relay

6. Evidence boundary: what this pinned source does and does not establish

The compact model is: Clerk identifies the account; DPoP proves the device; the relay authorizes and launches; the tunnel exposes; the environment admits; the client and environment do the work. Drawing the relay across every subsequent arrow makes the architecture less secure to reason about and less true to the code.

T3
Source-locked editionRead against fa219001d · 23 Aug 2026
Book search

Find a concept, module, or source path

Type two or more characters.