JSON Web Tokens (JWT) are widely used for securing APIs and managing identity and access. While their primary role is to authenticate users, JWTs can also support fine-grained authorization — making it possible to control access down to the resource, action, or field level. This blog explores how to implement permission granularity using JWT in a secure and scalable way.
What Is Fine-Grained Access Control?
Fine-grained access control (FGAC) goes beyond coarse rules like “admin vs user” roles. It enables you to define access at the level of:
- Specific endpoints or API operations (e.g.,
/invoices/view
) - Specific fields in a document or data model
- Time, context, or tenant-specific restrictions
Instead of saying who can access the system, FGAC defines what each user can do within it.
JWT Basics Refresher
A JSON Web Token has three parts:
<Header>.<Payload>.<Signature>
The Payload
typically contains claims like:
{
"sub": "user123",
"role": "editor",
"permissions": ["read:articles", "edit:comments"],
"iat": 1715751981,
"exp": 1715755581
}
The permissions
claim here illustrates a simple form of authorization. These claims can be validated server-side without additional database queries, supporting stateless authentication.
Using JWT for Permission Granularity
JWT can encode complex authorization models by including structured claims. For example:
{
"sub": "alice",
"department": "finance",
"permissions": {
"invoices": {
"view": true,
"edit": false
},
"reports": {
"view": true,
"export": true
}
}
}
This design allows you to:
- Limit access by resource type (
invoices
,reports
) - Restrict actions per resource (
view
,edit
,export
) - Apply policy logic directly in the backend using decoded JWT claims
Mermaid Flowchart: JWT Access Evaluation Logic
This logic is usually executed inside a middleware layer in Node.js, Python, Java, or Go APIs.
Case Study: Role + Permission-Based Claims
Scenario: A company has three roles: viewer, editor, and admin. Each role has different access levels.
JWT payload example for an editor
:
{
"sub": "editor42",
"role": "editor",
"permissions": ["articles:read", "comments:edit"]
}
On the backend:
function checkPermission(tokenPayload, resource, action) {
const permissionKey = `${resource}:${action}`;
return tokenPayload.permissions.includes(permissionKey);
}
This design avoids repeated lookups and supports microservice-friendly architectures.
Security Considerations 🔒
- Never trust client-side JWTs blindly — always verify the signature.
- Use short expiration times and rotate signing keys periodically.
- Encrypt sensitive claims if your token contains confidential data (e.g., with JWE).
- Avoid token bloat: excessively large tokens degrade performance and leak metadata.
Alternatives and Extensions
- Policy-as-code: Tools like OPA (Open Policy Agent) allow decoupled policy evaluation.
- Attribute-Based Access Control (ABAC): Add context like time, IP, or location into claims.
- Scopes vs. Permissions: OAuth 2.0 uses scopes, but scopes are often too broad for fine-grained control.
Final Thoughts
JWTs are more than just authentication tokens. When used wisely, they become powerful vehicles for precise, low-latency access control. This makes them ideal for modern, distributed applications — especially in zero-trust environments.
➡️ What is the balance between token size and permission depth in your system? ➡️ Can dynamic permissions (e.g., project-based access) be encoded securely in JWTs, or is an external policy engine required?
Fine-grained access is no longer a luxury. It’s a necessity — and JWT can be your ally.