From 51a3ab8dd5d9ef4fd1c7642ee58e057b8ae30fb4 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Wed, 1 Jul 2026 08:06:40 +0100 Subject: [PATCH 1/3] Add skill notes on using JWT auth --- .claude/skills/build-plugin/SKILL.md | 2 +- .../build-plugin/references/metadata.md | 41 +++++++++++++++++++ .claude/skills/build-plugin/references/ui.md | 2 +- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/.claude/skills/build-plugin/SKILL.md b/.claude/skills/build-plugin/SKILL.md index 707b2bbd..d508a634 100644 --- a/.claude/skills/build-plugin/SKILL.md +++ b/.claude/skills/build-plugin/SKILL.md @@ -68,7 +68,7 @@ Before writing a single file, understand and explore the API. **Use `AskUserQues - **Time-range control** — does the endpoint accept a queryable time range at all (a `from`/`to`, `start`/`end`, or `period` parameter), or only return a fixed snapshot / current values? - **Data granularity** — when the endpoint _does_ accept a range, the finest interval it aggregates at: **per-event/raw**, **hourly**, **daily**, or **monthly**. Read it off the API docs (aggregation windows, `granularity`/`interval` params, the minimum queryable range). 5. **Understand pagination** — Cursor/next-token, or offset/limit? Separate concern from response transformation. -6. **Note the auth pattern** — API key in header, Bearer token, OAuth2, Basic auth? Determine from the docs. +6. **Note the auth pattern** — API key in header, Bearer token, OAuth2, Basic auth, JWT Bearer (signed-JWT auth)? Determine from the docs. --- diff --git a/.claude/skills/build-plugin/references/metadata.md b/.claude/skills/build-plugin/references/metadata.md index 06c6300a..c0eb951e 100644 --- a/.claude/skills/build-plugin/references/metadata.md +++ b/.claude/skills/build-plugin/references/metadata.md @@ -151,3 +151,44 @@ OAuth URLs and scopes support `{{fieldName}}` expressions — useful when the au "oauth2AuthUrl": "https://{{accountId}}.example.com/oauth/authorize", "oauth2Scope": "read {{role ? 'role:' + role : ''}}" ``` + +**JWT Bearer** (HMAC — `HS256`/`HS384`/`HS512`; distinct from the static "Bearer token" pattern above — this signs a fresh JWT and attaches it to every request): + +```json +"authMode": "jwtBearer", +"jwtAlgorithm": "HS256", +"jwtSecret": "{{jwtSecret}}", +"jwtPayload": { + "iss": "my-integration", + "sub": "{{accountId}}", + "iat": "{{Math.floor(Date.now() / 1000) - 60}}", + "exp": "{{Math.floor(Date.now() / 1000) + 600}}" +} +``` + +**JWT Bearer** (asymmetric — `RS256`/`RS384`/`RS512`, `PS256`/`PS384`/`PS512`, `ES256`/`ES384`/`ES512`): + +```json +"authMode": "jwtBearer", +"jwtAlgorithm": "RS256", +"jwtPrivateKey": "{{jwtPrivateKey}}", +"jwtPayload": { + "iss": "my-integration", + "iat": "{{Math.floor(Date.now() / 1000) - 60}}", + "exp": "{{Math.floor(Date.now() / 1000) + 600}}" +} +``` + +Set exactly one of `jwtSecret` (HMAC algorithms) or `jwtPrivateKey` — a PEM-encoded private key — for every other algorithm; never both. There's no caching/refresh step to configure — unlike OAuth2, a fresh JWT is signed on every request. + +**Advanced JWT options:** + +```json +"jwtTokenLocation": "queryParam", // "header" (default) or "queryParam" +"jwtQueryParamName": "token", // required when jwtTokenLocation is "queryParam" — the query param the JWT is sent as +"jwtSecretIsBase64": true, // set when jwtSecret is base64-encoded rather than plain text +"jwtHeaderPrefix": "Bearer ", // text prepended to the token when sent as a header; defaults to "Bearer " (include the trailing space if overriding) +"jwtHeaders": { "kid": "my-key-id" } // extra claims merged into the JWT's own header (not the HTTP request header), e.g. a key ID +``` + +`jwtPayload` and `jwtHeaders` are plain JSON objects; any string value can itself be a `{{ ... }}` expression, evaluated fresh on every request — this is how `iat`/`exp` stay current without any extra logic. diff --git a/.claude/skills/build-plugin/references/ui.md b/.claude/skills/build-plugin/references/ui.md index acbb9b33..8a9c8551 100644 --- a/.claude/skills/build-plugin/references/ui.md +++ b/.claude/skills/build-plugin/references/ui.md @@ -135,7 +135,7 @@ The `validation` object accepts [react-hook-form](https://react-hook-form.com/do } ``` -**`password`** — masked text; **use for any API key, token, secret, or password field**: +**`password`** — masked text; **use for any API key, token, secret, or password field** — including a JWT signing secret or private key (see [JWT Bearer](metadata.md#auth-patterns) in metadata.md): ```json { "type": "password", "name": "apiKey", "label": "API Key" } From b821ed7e73b923492b24690910d56e74ba887cc3 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 3 Jul 2026 13:18:42 +0100 Subject: [PATCH 2/3] Apply suggestion from @coderabbitai[bot] Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .claude/skills/build-plugin/references/metadata.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.claude/skills/build-plugin/references/metadata.md b/.claude/skills/build-plugin/references/metadata.md index c0eb951e..a13502fb 100644 --- a/.claude/skills/build-plugin/references/metadata.md +++ b/.claude/skills/build-plugin/references/metadata.md @@ -185,6 +185,7 @@ Set exactly one of `jwtSecret` (HMAC algorithms) or `jwtPrivateKey` — a PEM-en ```json "jwtTokenLocation": "queryParam", // "header" (default) or "queryParam" +// Prefer "header"; query params can leak the token via logs/referrers. "jwtQueryParamName": "token", // required when jwtTokenLocation is "queryParam" — the query param the JWT is sent as "jwtSecretIsBase64": true, // set when jwtSecret is base64-encoded rather than plain text "jwtHeaderPrefix": "Bearer ", // text prepended to the token when sent as a header; defaults to "Bearer " (include the trailing space if overriding) From 679e7c641104df015d6f48d110d3b49c36a9ef01 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 3 Jul 2026 13:20:15 +0100 Subject: [PATCH 3/3] remove cross-reference of auth, skill can infer to use password field --- .claude/skills/build-plugin/references/ui.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/skills/build-plugin/references/ui.md b/.claude/skills/build-plugin/references/ui.md index 8a9c8551..db873c5e 100644 --- a/.claude/skills/build-plugin/references/ui.md +++ b/.claude/skills/build-plugin/references/ui.md @@ -135,7 +135,7 @@ The `validation` object accepts [react-hook-form](https://react-hook-form.com/do } ``` -**`password`** — masked text; **use for any API key, token, secret, or password field** — including a JWT signing secret or private key (see [JWT Bearer](metadata.md#auth-patterns) in metadata.md): +**`password`** — masked text; **use for any API key, token, secret, or password field** ```json { "type": "password", "name": "apiKey", "label": "API Key" }