Skip to content

fix(auth): let an Auth0 RBAC admin role reach the token - #385

Open
schronck wants to merge 4 commits into
mainfrom
fix/auth0/rbac-roles-without-an-action
Open

fix(auth): let an Auth0 RBAC admin role reach the token#385
schronck wants to merge 4 commits into
mainfrom
fix/auth0/rbac-roles-without-an-action

Conversation

@schronck

Copy link
Copy Markdown
Collaborator

Summary

With DECPM_ADMIN_ROLE set on an Auth0 node, no user could pass require_admin, so POST /network-config, /kick and post-bootstrap PUT /party-config were unreachable. Auth0 returns a permission in scope only when the client requested it, and the SPA requested nothing beyond the default openid profile email.

Two carriers, so an operator can pick per tenant:

  • permissions — read the array Auth0 emits under access_token_authz. Auth0 does not filter it by requested scope, so an RBAC-assigned role arrives with no post-login Action. This is the path that needs no Auth0 Action at all.
  • DECPM_AUTH0_SCOPE — extra scopes plumbed cli.rsAuth0Config/auth-configauthorizationParams.scope, for tenants that grant the role as a resource-server scope.

DECPM_JWT_ROLE_CLAIM (#378) already covers the namespaced-custom-claim path. The three are complementary; the docs now present all three and lead with the one that needs no Action.

Related issues

Closes #383

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • Build / CI / chore

Checklist

  • cargo fmt -- --check passes
  • cargo clippy --all-targets --all-features -- -D warnings is clean
  • cargo test passes
  • Added/updated tests where appropriate
  • Updated documentation where appropriate
  • Commits follow the <type>(<scope>): <subject> convention

Notes for reviewers

Two decisions worth a look.

permissions is read from the flattened claim map, not a typed field. A typed Option<Vec<String>> would fail the whole decode::<Claims> on any provider that emits a different shape under that name, turning a cosmetic mismatch into a total lockout of a healthy node. malformed_permissions_claim_still_verifies pins that.

The frontend re-states openid profile email when a scope is set. auth0-spa-js 2.19.2 builds options as Object.assign({}, defaultOptions.authorizationParams, options.authorizationParams), a shallow merge, so a supplied scope replaces the default outright — its own typings say so. A plain passthrough would have silently stripped the email claim Principal reads.

cargo test is unchecked because I did not run the suite locally; clippy --all-targets compiles the new tests, and CI will execute them. Five added: permissions grants the role, malformed permissions still verifies, permissions/scope dedupe, and /auth-config surfacing vs omitting the scope.

The reporter on #383 offered to test whichever shape we picked, so worth a devnet image for them.

@schronck
schronck requested a review from a team August 27, 2026 09:55
@schronck schronck self-assigned this Aug 27, 2026
@schronck
schronck requested review from scolear and sosaucily and a lite review from Copilot August 27, 2026 09:55

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.

Pull request overview

This PR fixes Auth0-admin authorization failures by adding support for Auth0 RBAC permissions[] as a role carrier in JWT validation, and by optionally plumbing a deploy-time Auth0 scope (DECPM_AUTH0_SCOPE) from CLI/config → backend /auth-config → frontend Auth0 SDK initialization. It also updates the deployment guide to document the three supported Auth0 “admin role” carriers.

Changes:

  • Extend JWT role extraction to read Auth0 RBAC permissions (in addition to existing roles, realm_access.roles, and scope carriers), with tests.
  • Add DECPM_AUTH0_SCOPE through config + /auth-config, and update the SPA to append it while preserving Auth0’s default openid profile email.
  • Update deployment documentation to explain the three carrier options and troubleshooting guidance.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
docs/DEPLOYMENT_GUIDE.md Documents Auth0 admin-role carrier options (permissions, scope, custom claim) and adds DECPM_AUTH0_SCOPE.
crates/decman/src/server/handlers/auth.rs Extends /auth-config response with auth0_scope and adds handler tests.
crates/decman/src/main.rs Wires CLI/env DECPM_AUTH0_SCOPE into Auth0Config and warns when scope is set without Auth0 config.
crates/decman/src/config.rs Adds scope: Option<String> to Auth0Config with documentation and updates config tests.
crates/decman/src/cli.rs Introduces hidden CLI/env option DECPM_AUTH0_SCOPE.
crates/decman/src/auth/validators/jwt.rs Reads Auth0 permissions[] into role collection and adds targeted JWT validator tests.
crates/decman/frontend/src/main.tsx If auth0_scope is present, requests openid profile email plus the configured scopes (avoiding auth0-spa-js default override).
crates/common/src/types.rs Adds auth0_scope to AuthConfigResponse (omitted when None).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/decman/src/auth/validators/jwt.rs Outdated

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.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (1)

crates/decman/src/auth/validators/jwt.rs:268

  • Restrict this carrier to the configured Auth0 API. find_trusted currently records only client_id/discovery data and JWT validation disables audience checking, so this block promotes permissions for every trusted issuer. The new test even uses a Keycloak validator. Consequently, a Keycloak token—or an Auth0 token for another API in the same tenant and SPA—with a matching permission can satisfy DECPM_ADMIN_ROLE. Preserve the matched provider and expected Auth0 audience, enforce that audience, and only merge permissions for that Auth0 match.
        if let Some(value) = claims.additional.get("permissions")
            && let Ok(permissions) = serde_json::from_value::<Vec<String>>(value.clone())
        {
            flat_roles.extend(permissions);

Auth0 returns a permission in `scope` only if the client requested it, so
an RBAC-assigned admin role never reached `require_admin`. The
`permissions` array that `access_token_authz` emits is not filtered that
way, so reading it lets the role arrive with no post-login Action.

Read from the flattened claim map rather than a typed field: a typed
`Option<Vec<String>>` would fail the whole token decode on a provider
that emits a different shape under that name, locking out every user of
an otherwise healthy node.
The SPA sent no `scope`, so an admin role granted as a resource-server
scope could not reach the token whatever the tenant was configured to
do. DECPM_AUTH0_SCOPE now travels to the browser over /auth-config.

auth0-spa-js shallow-merges authorizationParams over its own defaults,
so a supplied `scope` replaces "openid profile email" outright. The
frontend re-states the default; without that, setting the variable would
strip the `email` claim `Principal` depends on.

Warn when the variable is set without DECPM_AUTH0_DOMAIN/CLIENT_ID,
mirroring DECPM_KEYCLOAK_INTERNAL_URL — a knob that is silently ignored
is how this class of problem costs operators an afternoon.
The guide said an Action was mandatory. RBAC permissions need none, so
lead with that and keep the scope and namespaced-claim paths as
alternatives. Point the 403 entry at the token's actual carriers.
Reading `permissions` for every trusted issuer let any provider that
emits a top-level `permissions` array satisfy DECPM_ADMIN_ROLE through
it, including a Keycloak realm with a custom mapper. `find_trusted` now
reports which provider matched and only the Auth0 arms enable the
carrier.

The carrier moves into a pure `auth0_permission_roles` so it can be
tested directly: `auth0_issuer_of` hardcodes https, so the Auth0 arm of
`find_trusted` is not reachable through the wiremock harness.
@schronck
schronck force-pushed the fix/auth0/rbac-roles-without-an-action branch from 65de142 to 0f0ffea Compare August 27, 2026 12:20
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.

Auth0: the SPA doesn't request a scope, so an RBAC-assigned admin role never reaches the token

2 participants