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..a13502fb 100644 --- a/.claude/skills/build-plugin/references/metadata.md +++ b/.claude/skills/build-plugin/references/metadata.md @@ -151,3 +151,45 @@ 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" +// 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) +"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..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**: +**`password`** — masked text; **use for any API key, token, secret, or password field** ```json { "type": "password", "name": "apiKey", "label": "API Key" }