Authorization becomes brittle, hard to audit, and difficult to change without waiting for token expiry or rewriting application logic. Teams lose centralized control, policy updates become slower, and access decisions can drift from current business rules. A better model is to keep JWTs as identity evidence and evaluate permissions in a separate authorization layer.
Why This Matters for Security Teams
When authorization decisions are encoded inside JWT claims and application logic, the token stops being a lightweight identity artifact and starts acting like a policy container. That creates hidden coupling: a claim that was correct at issuance can become stale as roles, entitlements, or business rules change. Teams then inherit brittle access control, slow policy updates, and a weaker audit trail because the effective decision is spread across code paths instead of a central control point.
This pattern also increases the blast radius of a mistake. If a claim is overtrusted, every service that reads it may inherit the same incorrect decision. If the application embeds permission checks in multiple places, a single policy change can require coordinated code updates, redeployment, and retesting across the stack. The result is usually not one obvious failure, but gradual drift between what the token says, what the code enforces, and what the business actually intends. In practice, many teams discover the problem only after a role change, incident review, or access exception reveals that enforcement is no longer aligned with current policy.
That is why JWTs should usually carry identity evidence and a minimal set of stable attributes, while authorization remains a separate decision that can be updated without waiting for token expiry.
How It Works in Practice
A JWT is best treated as a signed statement about the subject, not as a complete authorization engine. If the application embeds permissions directly into claims, the token becomes a snapshot of access state at issuance time. That snapshot can be useful for short-lived decisions, but it is a poor fit for permissions that change frequently, depend on context, or need centralized governance.
In practice, the failure modes usually look like this:
- Claims such as admin, can_edit, or department are trusted as if they were current policy decisions.
- Application code duplicates authorization rules across controllers, services, or microservices.
- Policy changes require code releases instead of a configuration or policy update.
- Revocation and privilege reduction lag until tokens expire or sessions are invalidated.
That creates a tension between performance and control. Static claim checks are fast, but they are only safe when the underlying permission rarely changes and the token lifetime is tightly bounded. Once entitlements are dynamic, the safer pattern is to validate the JWT for identity and integrity, then call a centralized authorization layer, policy engine, or fine-grained access control service to make the current decision. That preserves auditability because the policy source of truth is separate from the request token.
Teams should also be careful not to confuse authentication freshness with authorization freshness. A validly signed token only proves the issuer created it and that it has not expired or been altered. It does not prove the business meaning of the embedded permissions is still correct. These controls tend to break down in distributed systems where multiple services independently cache claims and no single layer owns the final access decision.
Common Variations and Edge Cases
Tighter token-based authorization often reduces runtime dependency on a central policy service, but that convenience comes with a real trade-off: the more authority you embed in the JWT, the harder it is to revoke, re-scope, or audit access quickly. In some systems, that trade-off is acceptable for coarse, low-risk entitlements with short-lived tokens; in others, it is a serious governance problem.
There are also cases where a claim is useful as an input, not a decision. For example, a token can carry tenant, issuer, or subject attributes that help the application look up policy, but the claim should not itself define the permission outcome. Best practice is evolving toward that split, especially in systems with delegated access, shared services, or multiple business rules applied to the same identity. The rule of thumb is simple: if the permission would be controversial in an access review, it should not live only in a token claim.
Edge cases appear when teams use long-lived refresh flows, offline APIs, or heavily cached edge services. In those environments, claim-based authorization can work only if revocation, token lifetime, and policy drift are tightly managed. Otherwise, a token may keep granting access long after the underlying entitlement has changed, which is exactly the kind of inconsistency attackers and auditors both notice.
Risk and Threat Considerations
Embedding authorization logic in JWT claims and application code creates a control-failure risk as well as a compromise risk. The main exposure is stale or overbroad access that persists because the token reflects an old decision and the application has no independent policy check to correct it. That makes privilege reduction, exception handling, and revocation slower than the business or threat model requires.
Failure mechanism: An attacker or mistake can exploit trust in a claim that was valid when issued but no longer matches current policy, especially when the application duplicates checks across services and treats the token as authoritative for access.
Impact: Unauthorized access can persist past role changes, access reviews become less reliable, and a policy correction may require code changes or token expiry before it takes effect.
Standards & Framework Alignment
This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.
NIST CSF 2.0, CIS Controls v8 and NIST SP 800-63 set the governance and control requirements practitioners need to meet.
| Framework | Control / Reference | Relevance |
|---|---|---|
| NIST CSF 2.0 | PR.AC — Identity Management, Authentication and Access Control | JWT claim handling directly affects access control and authorization governance. |
| Recommendation — Centralize authorization decisions and enforce least privilege across services. | ||
| CIS Controls v8 | 6 — Access Control Management | This pattern concerns who can access what and how quickly access changes take effect. |
| Recommendation — Review and revoke access through a governed control process, not embedded code. | ||
| NIST SP 800-63 | AAL — Authentication Assurance Level | JWTs carry identity evidence, so assurance and token trust boundaries matter. |
| Recommendation — Bound token use to the assurance level needed for the protected resource. | ||
Practitioner Guidance
What to prioritise: Keep JWTs narrow. Use them to establish identity, issuer trust, and only the minimum stable attributes needed for routing or coarse context. Put changing permissions, resource-level checks, and exception handling in a centralized authorization layer.
What to verify: Confirm that no critical access decision depends solely on a token claim that can outlive the business rule it represents. If the answer is yes, treat that as a design defect, not an implementation detail.
Common mistake: Teams often assume a signed claim is equivalent to an up-to-date entitlement. It is not. A signed token can still be stale, and stale authorization is where most drift starts.
Practitioner takeaway: The safest design is the one that lets identity travel with the request while policy remains changeable, reviewable, and enforceable in one place.
Related resources from NHI Mgmt Group
- What is the difference between querying permissions through PostgreSQL and storing authorization logic directly in application code?
- Why does embedding authorization logic directly in application code create risk at scale?
- How should security teams find authorization logic hidden in application code?
- What should IAM teams do before moving authorization logic out of application code?