Skip to content

[TT-17312] Resolve JWT scope claim name from client-IdP registry#8296

Merged
mativm02 merged 3 commits into
masterfrom
feat/TT-17312/idp-registry-scope-claim-name
Jun 9, 2026
Merged

[TT-17312] Resolve JWT scope claim name from client-IdP registry#8296
mativm02 merged 3 commits into
masterfrom
feat/TT-17312/idp-registry-scope-claim-name

Conversation

@mativm02

@mativm02 mativm02 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Description

A registry-bound IdP can declare a non-default scope_claim_name (e.g. scp for Entra/Azure AD, roles). Until now the gateway resolved the scope claim name only from the API def / OAS, defaulting to "scope". So a registry-only API whose IdP emits scopes under a non-scope claim — with no manual scope config — would 403 (no matching policy found) because the gateway read the wrong claim.

This wires the dashboard's new scope_claim_name field (TykTechnologies/tyk-analytics#5788) through the gateway registry so it's actually consumed.

Changes

  • IdP gains ScopeClaimName (json:"scope_claim_name"), mirroring the dashboard model.ClientIdP and the MDCB model (tyk-sink#794). Picked up automatically on both feeds (Dashboard envelope + RPC unmarshalIdPs) — no transport change.
  • Binding denormalises it from the IdP at rebuild time, so the JWT hot path needs no second registry lookup (same approach already used for ScopeToPolicy).
  • scopeClaimNameForRequest resolves with manual-wins precedence: Classic GetScopeClaimName / OAS → registry binding → "scope". The fallback only fires when the API def sets nothing, so manual-config APIs stay byte-identical.

Cross-repo

Repo PR Field
tyk-analytics (dashboard) #5788 model.ClientIdP.ScopeClaimName
tyk-sink (MDCB) #794 models.ClientIdP.ScopeClaimName
tyk (gateway) this PR IdP.ScopeClaimName

Verified end-to-end: the MDCB RPC read path (LoadClientIdPsfilterAndPruneClientIdPspruneIdP) uses full-document loads and shallow struct copies, so the field is carried through with no projection dropping it.

How this has been tested

New tests (TDD, race-clean):

  • TestIdPRegistry_RebuildCarriesScopeClaimName — propagation into the binding.
  • TestScopeClaimNameForRequest — the three-tier resolution order (manual wins → binding → default).
  • TestIdPRegistry_JWT_ResolvesScopeClaimNameFromRegistry — end-to-end: token with scopes under scp, no manual scope claim → 200.
  • TestIdPRegistry_JWT_NoScopeClaimNameRejects — counterpart proving the same setup with no IdP scope_claim_name falls through to "scope" and 403s, pinning down that the registry fallback is what flips the result.

Types of changes

  • New feature (non-breaking change which adds functionality)

A registry-bound IdP can declare a non-default scope_claim_name (e.g. "scp"
for Entra/Azure AD, "roles"). When the API def sets no scope claim name, the
gateway now falls back to the bound IdP's claim before defaulting to "scope".

Without this, a registry-only API whose IdP emits scopes under a non-"scope"
claim would 403 ("no matching policy found") because the gateway read the
wrong claim.

- IdP gains ScopeClaimName (json: scope_claim_name), mirroring the dashboard
  model.ClientIdP and the MDCB model (tyk-sink #794).
- Binding denormalises it at rebuild time so the JWT hot path needs no second
  registry lookup.
- scopeClaimNameForRequest resolves with manual-wins precedence: API-def /
  OAS -> registry binding -> "scope". Manual-config APIs stay byte-identical.
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

🎯 Recommended Merge Targets

Based on JIRA ticket TT-17312: Gateway: client IdP registry + JWT integration

Fix Version: Tyk 5.14.0

⚠️ Warning: Expected release branches not found in repository

Required:

  • master - No matching release branches found. Fix will be included in future releases.

📋 Workflow

  1. Merge this PR to master first

@probelabs

probelabs Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

This PR enhances the JWT middleware to resolve the scope claim name from the client-IdP registry. Previously, the gateway only checked the API definition or used a default value ("scope"), which caused integration issues with Identity Providers like Azure AD that use non-standard claim names (e.g., "scp"). This change introduces a fallback mechanism to use the scope_claim_name from the IdP configuration, ensuring broader compatibility.

Files Changed Analysis

  • gateway/idp_registry.go: The IdP and Binding data structures are extended with a ScopeClaimName field. The rebuild function is updated to denormalize this field from the IdP configuration into the API binding. This is a performance optimization to avoid extra lookups on the request hot path.
  • gateway/mw_jwt.go: The core logic is implemented in the new scopeClaimNameForRequest function. It establishes a clear, three-tier resolution order for the scope claim name: first, the manual configuration in the API definition; second, the value from the IdP registry binding; and finally, the default value "scope".
  • gateway/idp_registry_test.go: Comprehensive tests have been added to validate the new functionality. This includes unit tests for the resolution logic's precedence, tests to ensure the ScopeClaimName is correctly denormalized, and end-to-end integration tests that confirm JWTs with custom scope claims are now correctly validated.

Architecture & Impact Assessment

  • What this PR accomplishes: It allows the gateway to correctly validate JWTs from IdPs that use a non-standard scope claim name by reading this configuration from the IdP registry. This makes JWT validation more flexible and robust.
  • Key technical changes introduced:
    1. Data Model Extension: The IdP and Binding structs now include ScopeClaimName to carry the configuration.
    2. Denormalization for Performance: The ScopeClaimName is copied into the Binding when the registry is rebuilt, ensuring fast access during request processing.
    3. Tiered Resolution Logic: A clear precedence is established for resolving the scope claim name: Manual API Config → IdP Registry Config → Default ("scope").
  • Affected system components:
    • JWT Middleware: The core JWT validation logic is modified to use the new scope claim resolution strategy.
    • IdP Registry: The in-memory store for IdP configurations is updated to handle and propagate the new field.
    • API Authentication: This affects any API using JWT authentication where scopes are managed via the IdP registry.

JWT Scope Claim Resolution Flow

sequenceDiagram
    participant Client
    participant Gateway
    participant JWTMiddleware
    participant IdPRegistry

    Client->>+Gateway: Request with JWT
    Gateway->>+JWTMiddleware: processCentralisedJWT(request)
    JWTMiddleware->>JWTMiddleware: scopeClaimNameForRequest()
    Note over JWTMiddleware: 1. Check API Definition for manual config
    alt Manual config exists
        JWTMiddleware-->>JWTMiddleware: Use value from API definition
    else No manual config
        Note over JWTMiddleware: 2. Check IdP Registry Binding
        JWTMiddleware->>IdPRegistry: Get Matched Binding
        IdPRegistry-->>JWTMiddleware: Return Binding
        alt Binding has ScopeClaimName
            JWTMiddleware-->>JWTMiddleware: Use value from Binding (e.g., "scp")
        else Binding has no value
            Note over JWTMiddleware: 3. Fallback to default
            JWTMiddleware-->>JWTMiddleware: Use default value ("scope")
        end
    end
    JWTMiddleware->>JWTMiddleware: Extract scopes from JWT using resolved claim name
    JWTMiddleware-->>-Gateway: Validation successful
    Gateway-->>-Client: 200 OK
Loading

Scope Discovery & Context Expansion

This change is part of a larger, cross-repository feature to support custom scope claim names, with related changes in tyk-analytics (Dashboard) and tyk-sink (MDCB). This PR implements the final consumption of that configuration within the gateway's JWT validation logic.

The impact is primarily contained within the JWT authentication flow for APIs associated with an Identity Provider through the registry. The change is backward-compatible, as the new logic only acts as a fallback and preserves the behavior of existing APIs with manual scope configurations.

Further investigation could involve checking how the IdP registry is populated (e.g., via Dashboard API or MDCB RPC calls) to ensure the new scope_claim_name field is correctly propagated from those sources into the gateway's in-memory registry.

Metadata
  • Review Effort: 3 / 5
  • Primary Label: feature

Powered by Visor from Probelabs

Last updated: 2026-06-09T19:14:37.332Z | Triggered by: pr_updated | Commit: 5a4f32c

💡 TIP: You can chat with Visor using /visor ask <your question>

@probelabs

probelabs Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

✅ Security Check Passed

No security issues found – changes LGTM.

✅ Architecture Check Passed

No architecture issues found – changes LGTM.

✅ Performance Check Passed

No performance issues found – changes LGTM.

✅ Quality Check Passed

No quality issues found – changes LGTM.


Powered by Visor from Probelabs

Last updated: 2026-06-09T19:14:36.086Z | Triggered by: pr_updated | Commit: 5a4f32c

💡 TIP: You can chat with Visor using /visor ask <your question>

@mativm02
mativm02 enabled auto-merge (squash) June 9, 2026 11:45
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

🚨 Jira Linter Failed

Commit: 5a4f32c
Failed at: 2026-06-09 19:13:26 UTC

The Jira linter failed to validate your PR. Please check the error details below:

🔍 Click to view error details
failed to get Jira issue: failed to fetch Jira issue TT-17312: Issue does not exist or you do not have permission to see it.: request failed. Please analyze the request body for more details. Status code: 404

Next Steps

  • Ensure your branch name contains a valid Jira ticket ID (e.g., ABC-123)
  • Verify your PR title matches the branch's Jira ticket ID
  • Check that the Jira ticket exists and is accessible

This comment will be automatically deleted once the linter passes.

@sonarqubecloud

sonarqubecloud Bot commented Jun 9, 2026

Copy link
Copy Markdown

Quality Gate Passed Quality Gate passed

Issues
0 New issues
0 Accepted issues

Measures
0 Security Hotspots
91.7% Coverage on New Code
0.0% Duplication on New Code

See analysis details on SonarQube Cloud

@mativm02
mativm02 merged commit f9ffed1 into master Jun 9, 2026
52 of 55 checks passed
@mativm02
mativm02 deleted the feat/TT-17312/idp-registry-scope-claim-name branch June 9, 2026 20:13
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