Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/jwks-kid-meaning.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
6 changes: 3 additions & 3 deletions src/commands/helpTopics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/commands/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
10 changes: 5 additions & 5 deletions src/core/managedConnect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -24,12 +24,12 @@ export async function resolveJwksKid(authServerUrl: string): Promise<string> {

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;
Expand Down
2 changes: 1 addition & 1 deletion verify/docker-compose.verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading