From 99f7c3b1e2c10f5eab5ed8093fdeacbaace75775 Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Mon, 12 Oct 2026 20:17:28 -0400 Subject: [PATCH] docs: describe JWKS_KID as the service token kid, not the signing key id --- .changeset/jwks-kid-meaning.md | 5 +++++ README.md | 2 +- src/commands/helpTopics.ts | 6 +++--- src/commands/init.test.ts | 2 +- src/core/managedConnect.ts | 10 +++++----- verify/docker-compose.verify.yml | 2 +- 6 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 .changeset/jwks-kid-meaning.md diff --git a/.changeset/jwks-kid-meaning.md b/.changeset/jwks-kid-meaning.md new file mode 100644 index 0000000..d3cc608 --- /dev/null +++ b/.changeset/jwks-kid-meaning.md @@ -0,0 +1,5 @@ +--- +"seamless-cli": patch +--- + +Describe `JWKS_KID` correctly. It is the `kid` header on the service tokens the adapter signs with `API_SERVICE_TOKEN`, and the auth server does not check it. The warning printed when a managed instance's key id cannot be read no longer says the adapter warns "until it matches", and `seamless help add` and the README call it the service token kid instead of the signing key id. diff --git a/README.md b/README.md index e785cbf..e4a5e19 100644 --- a/README.md +++ b/README.md @@ -316,7 +316,7 @@ It finds a backend and a web app, at the project root or in `api/`, `server/`, ` The backend can be Express or Fastify, Go (net/http, Gin, chi or Echo), Rust (Axum), or Python (FastAPI or Django). The web app can be React, Angular, Vue or SvelteKit. - connects an auth server. `--local` writes `seamless/docker-compose.yml` (the auth server and its Postgres) with its secrets in `seamless/.env`, which is gitignored. `--app` connects a managed application instead. -- writes the backend's `.env` (auth server URL, service token, signing key id, and a cookie secret if you have none) and the web app's API URL in `.env.local` (Angular, which does not read `.env`, gets it in the printed code). It keeps every value you already have and every comment. +- writes the backend's `.env` (auth server URL, service token, service token kid, and a cookie secret if you have none) and the web app's API URL in `.env.local` (Angular, which does not read `.env`, gets it in the printed code). It keeps every value you already have and every comment. - installs the adapter and the web binding (with `vue-router` or `@angular/router` when the app has no router yet) with your own tool: npm, pnpm, yarn or bun, `go get`, `cargo add`, `uv add` or `poetry add`. A plain pip project gets the `pip install` printed, since only you know which environment it belongs in. `--skip-install` prints every command instead. - prints the lines to add to your server and to your web app, file by file: the provider or plugin, the bundled sign-in screens as routes, and guards for signed-in and signed-out pages. Each comes in the project's own language, TypeScript or JavaScript to match it. It never edits your source files. diff --git a/src/commands/helpTopics.ts b/src/commands/helpTopics.ts index 45a10a8..a8437da 100644 --- a/src/commands/helpTopics.ts +++ b/src/commands/helpTopics.ts @@ -118,9 +118,9 @@ Django) and a web app (React, Angular, Vue or SvelteKit), at the project root or api/, server/, backend/, web/, client/, frontend/, apps/* or packages/*, then: • Connects an auth server: a local one in Docker, written to seamless/, or a managed application - • Writes the backend's .env (auth server URL, service token, signing key id, a - cookie secret), keeping every value you already have, and the web app's API - URL in .env.local (Angular gets it in the printed code) + • Writes the backend's .env (auth server URL, service token, service token + kid, a cookie secret), keeping every value you already have, and the web + app's API URL in .env.local (Angular gets it in the printed code) • Installs the adapter and SDK with the project's own tool (npm, pnpm, yarn, bun, go get, cargo add, uv or poetry; a pip install is printed instead) • Prints the lines of code to add. It never edits your source files diff --git a/src/commands/init.test.ts b/src/commands/init.test.ts index 409a255..e475d2a 100644 --- a/src/commands/init.test.ts +++ b/src/commands/init.test.ts @@ -1968,7 +1968,7 @@ describe("managed scaffold JWKS kid", () => { expect.anything(), expect.objectContaining({ jwksKid: "dev-main" }), ); - expect(out()).toContain("Could not read the signing key id"); + expect(out()).toContain("Could not read the active key id"); }); it("does not fail the scaffold when the kid cannot be read", async () => { diff --git a/src/core/managedConnect.ts b/src/core/managedConnect.ts index f5db94a..cd166f7 100644 --- a/src/core/managedConnect.ts +++ b/src/core/managedConnect.ts @@ -10,9 +10,9 @@ import { requireInteractive } from "./tty.js"; // Shared by `seamless init` and `seamless add`, which both connect a project to a // managed application. -// Only reached when the instance cannot be asked. Adapters resolve the signing key -// from the token header through the remote JWKS, so this value verifies nothing, but -// they warn on boot while it is the dev default, so the real kid is worth fetching. +// Only reached when the instance cannot be asked. JWKS_KID is only the kid header on +// the service tokens the adapter signs, and the auth API never checks it, but adapters +// warn on boot while it is the dev default, so the instance's kid is worth fetching. export const FALLBACK_JWKS_KID = "dev-main"; // Asked of the instance rather than assumed: instances pin their kid per tier @@ -24,12 +24,12 @@ export async function resolveJwksKid(authServerUrl: string): Promise { console.log( kleur.yellow( - `Could not read the signing key id from ${authServerUrl}, so JWKS_KID is set to "${FALLBACK_JWKS_KID}".`, + `Could not read the active key id from ${authServerUrl}, so JWKS_KID is set to "${FALLBACK_JWKS_KID}".`, ), ); console.log( kleur.dim( - " Nothing verifies against it, the SDK resolves the key from the token, but your adapter will warn on boot until it matches. Read it from /.well-known/jwks.json once the instance is up.", + " JWKS_KID is only the kid header on the service tokens your adapter signs, and the auth server does not check it, but the adapter warns on boot while it is dev-main. Set any other value to quiet it.", ), ); return FALLBACK_JWKS_KID; diff --git a/verify/docker-compose.verify.yml b/verify/docker-compose.verify.yml index d517e15..1edd69d 100644 --- a/verify/docker-compose.verify.yml +++ b/verify/docker-compose.verify.yml @@ -18,7 +18,7 @@ # SEAMLESS_SVELTE_DIR absolute path to the Svelte app to serve (svelte and # svelte-dev profiles); defaults to ./svelte-app # API_SERVICE_TOKEN shared secret between the API and both adapters -# JWKS_KID key id the adapters expect (must match the API's active kid) +# JWKS_KID kid header on the adapters' service tokens (the API never checks it) # OWNER_EMAIL tenant owner; this email gets the admin role at signup # # The adapters are built from ./adapter-app and ./adapter-fastify-app in this repo,