Skip to content

docs: Elements reads the user key from the environment's user key claim (PER-17237) - #686

Open
CarlosMion wants to merge 1 commit into
masterfrom
carlos/per-17237-elements-user-key-claim-docs
Open

CarlosMion wants to merge 1 commit into
masterfrom
carlos/per-17237-elements-user-key-claim-docs

Conversation

@CarlosMion

Copy link
Copy Markdown
Contributor

Summary

  • Documents user_key_claim in an environment's JWKS configuration: the JWT claim that Elements login (LoginMethod.frontendOnly) and JWT invite approval read the Permit user key from. It is sub unless set.
  • Lists the rules for choosing the claim: only a claim the identity provider sets, a string or integer value, email only with a boolean email_verified: true, issuer and audience required alongside it on shared identity providers, and the re-keying effect of changing it. Shows how to clear it.
  • Marks the userKeyClaim login parameter deprecated, and points the Elements pages at the environment setting instead.

PER-17237.

Changes

File Change
docs/how-to/enforce-permissions/url-mapping/configuring-jwks.mdx New section "Set the user key claim" (#user-key-claim): the API request, the rules, clearing it, and how the userKeyClaim parameter relates to it.
docs/embeddable-uis/element-login.mdx userKeyClaim row: deprecated, links to the new section. The frontendOnly tab's key-matching sentence names the environment's user key claim.
docs/embeddable-uis/embedding-elements.mdx Step 1's claim sentence and the userKeyClaim row point at the environment setting.
docs/overview/access-requests-and-approvals.mdx Step 1.3 points at the environment setting.

The section documents the API only. The dashboard's JWKS dialog gains a matching field in a separate change, and the page can describe it once that field is live.

🤖 Generated with Claude Code

Document `user_key_claim` in the environment's JWKS configuration: the
claim Elements login and JWT invite approval read the Permit user key
from (`sub` by default), the rules for choosing it, and how to clear it.
Mark the `userKeyClaim` login parameter deprecated and point the
Elements pages at the environment setting.
@CarlosMion CarlosMion self-assigned this Nov 9, 2026
Copilot AI balanced review requested due to automatic review settings November 9, 2026 17:42
@linear-code

linear-code Bot commented Nov 9, 2026

Copy link
Copy Markdown

PER-17237

@netlify

netlify Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for permitio-docs ready!

Name Link
🔨 Latest commit bb3e455
🔍 Latest deploy log https://app.netlify.com/projects/permitio-docs/deploys/6af205f313da0a9cf46287ff
😎 Deploy Preview https://deploy-preview-686--permitio-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@CarlosMion
CarlosMion requested a review from zeevmoney November 9, 2026 17:44

@zeevmoney zeevmoney left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested — 1 HIGH, 3 LOW.

Blocking:

  • HIGH docs/how-to/enforce-permissions/url-mapping/configuring-jwks.mdx:96 — drop the conditional last sentence, so the paragraph keeps the contract the API reference states

Non-blocking:

  • LOW docs/how-to/enforce-permissions/url-mapping/configuring-jwks.mdx:85 — say that the jwks object replaces the stored configuration
  • LOW docs/how-to/enforce-permissions/url-mapping/configuring-jwks.mdx:89 — say the string-or-integer rule is about the claim's value in the token
  • LOW docs/how-to/enforce-permissions/url-mapping/configuring-jwks.mdx:91 — say what issuer and audience are and where they go

Details are in the inline comments on each line.


To go back to `sub`, send `"user_key_claim": null`. Removing the whole configuration with `"jwks": null` removes it as well.

`permit.elements.login()` and the invite approval request also accept a `userKeyClaim` (`user_key_claim`) parameter. Set the claim on the environment instead. If you pass the parameter, it must be `sub` or the environment's `user_key_claim`. When the environment sets `user_key_claim`, Permit rejects any other value.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Drop the conditional last sentence

Problem: The sentence before it already gives the rule for this parameter: if you pass it, it must be sub or the environment's user_key_claim. The API reference for the deprecated parameter states the same rule with no condition. The last sentence restates it but makes it depend on the environment setting, so this page and the API reference no longer describe the same contract.

Suggestion: Delete "When the environment sets user_key_claim, Permit rejects any other value." If the paragraph should still say what happens, fold it into the sentence before, with no condition:

`permit.elements.login()` and the invite approval request also accept a `userKeyClaim` (`user_key_claim`) parameter. Set the claim on the environment instead. If you pass the parameter, it must be `sub` or the environment's `user_key_claim`, or Permit rejects the request.

Introduced in bb3e455

curl -X PATCH 'https://api.permit.io/v2/projects/{proj_id}/envs/{env_id}' \
-H 'authorization: Bearer API_SECRET_KEY' \
-H 'Content-Type: application/json' \
-d '{"jwks": {"url": "<YOUR_JWKS_URL>", "user_key_claim": "<CLAIM_NAME>"}}'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Say that the jwks object replaces the stored configuration

Problem: A PATCH replaces the stored jwks object with the one in the request. Only issuer, audience and user_key_claim are kept when the request leaves them out. Readers who copy this example may not expect three things:

  • an environment set up with inline jwks.keys has to send its keys again (the example shows only url);
  • a custom ttl goes back to 600 unless it is sent again;
  • a jwks object that carries only user_key_claim is rejected with 422. That includes the "user_key_claim": null on line 94, which also needs the url or jwks.keys.

Suggestion: Add a sentence after the example, and point line 94 at it:

The `jwks` object you send replaces the stored one, so include your current `url` or `jwks.keys`, and your `ttl` if you changed it. Permit keeps the stored `issuer`, `audience` and `user_key_claim` when you leave them out.

At line 94, for example: "To go back to sub, send the same configuration with "user_key_claim": null."

Introduced in bb3e455

```

- **Name only a claim your identity provider sets.** Permit refuses claims that the user or the client can set, such as an OIDC `nonce`, `name` or `aud`. A custom claim filled from profile data the user can edit is just as unsafe.
- **The value must be a non-empty string or an integer.** Permit reads an integer as its decimal string.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Say this rule is about the claim's value in the token

Problem: "The value" can be read as the value of the user_key_claim setting. The setting only takes a string: the claim's name. The string-or-integer rule applies to the value the token carries in that claim.

Suggestion:

- **The claim's value in the token must be a non-empty string or an integer.** Permit reads an integer as its decimal string.

Introduced in bb3e455

- **Name only a claim your identity provider sets.** Permit refuses claims that the user or the client can set, such as an OIDC `nonce`, `name` or `aud`. A custom claim filled from profile data the user can edit is just as unsafe.
- **The value must be a non-empty string or an integer.** Permit reads an integer as its decimal string.
- **`email` needs a verified email.** Permit accepts the `email` claim only from a token whose `email_verified` claim is the JSON boolean `true`. Tokens without `email_verified`, such as Entra ID's, or with the string `"true"` can't use it. A user keyed by email changes key when their email changes at the identity provider.
- **On a shared identity provider, also set `issuer` and `audience`.** For a JWKS URL on Entra ID, Google, Apple, Salesforce, LinkedIn or Facebook, a request that sets or changes `user_key_claim` needs `issuer` and `audience` in the same configuration.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Say what issuer and audience are and where they go

Problem: No other docs page mentions the issuer and audience fields. Readers on Entra ID or Google who hit this rule aren't told three things:

  • the fields go in the same jwks object;
  • issuer is the iss their tokens carry, compared exactly (a list is accepted);
  • audience is a value of the tokens' aud.

While user_key_claim is set on one of these URLs, an update that changes or clears issuer or audience must also keep both set.

Suggestion: Name the fields and show them in a request body, for example:

- **On a shared identity provider, also set `issuer` and `audience`.** For a JWKS URL on Entra ID, Google, Apple, Salesforce, LinkedIn or Facebook, a request that sets or changes `user_key_claim` needs both in the same `jwks` object: `issuer` is the `iss` your tokens carry, and `audience` is a value of their `aud`. For example: `{"jwks": {"url": "<YOUR_JWKS_URL>", "issuer": "<YOUR_ISSUER>", "audience": "<YOUR_AUDIENCE>", "user_key_claim": "<CLAIM_NAME>"}}`. While `user_key_claim` is set, neither can be cleared.

Introduced in bb3e455

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants