JWT Claims — Standard and Custom Claims for User Identity and Authorization
In this tutorial, you will learn about JWT Claims. We cover key concepts, practical examples, and best practices to help you master this topic.
JWT claims are statements about the user and token, divided into registered claims (standardized), public claims (IANA-registered or URL-namespaced), and private claims (custom between parties).
What You'll Learn
The complete list of registered claims, how to design custom claims, claim validation, and best practices for claim naming to avoid conflicts.
Why It Matters
Claims are the data payload of JWT. Well-designed claims carry all the information needed for authorization without database lookups. Poorly designed claims cause security gaps or bloated tokens.
Real-World Use
Google's ID tokens include sub, email, email_verified, name, picture, locale. GitHub tokens include app_id and installation_id. Durga Antivirus Pro tokens include role, tenant_id, and permissions claims for fine-grained access control.
flowchart LR
A["JWT Claims"] --> B["Registered\niss, sub, aud, exp"]
A --> C["Public\nname, email, picture"]
A --> D["Private\nrole, tenant_id"]
B --> E["Standard, always validated"]
C --> F["IANA-registered or URL"]
D --> G["Custom between parties"]
style A fill:#dbeafe,stroke:#2563eb
style B fill:#fef3c7,stroke:#d97706
style C fill:#dcfce7,stroke:#16a34a
style D fill:#fef3c7,stroke:#d97706
Registered Claims
| Claim | Full Name | Purpose |
|---|---|---|
iss |
Issuer | Who issued the token |
sub |
Subject | Who the token is about (user ID) |
aud |
Audience | Who should accept the token |
exp |
Expiration | When the token expires |
nbf |
Not Before | When the token becomes valid |
iat |
Issued At | When the token was issued |
jti |
JWT ID | Unique identifier for this token |
typ |
Type | Token type (Access+ID, etc.) |
Public Claims
Standardized claims for user profile information:
{
"name": "Alice Johnson",
"given_name": "Alice",
"family_name": "Johnson",
"email": "alice@example.com",
"email_verified": true,
"picture": "https://example.com/avatar.jpg",
"locale": "en-US",
"updated_at": 1618000000
}
Private Claims (Custom)
Custom claims shared between your auth server and resource servers:
{
"role": "admin",
"tenant_id": "tenant-abc",
"permissions": ["threat:read", "threat:write", "users:read"],
"feature_flags": ["beta-dashboard", "new-search"],
"session_id": "sess-xyz"
}
Code Example: Validating Custom Claims
import jwt
from flask import Flask, request, jsonify, g
app = Flask(__name__)
SECRET = "your-secret"
REQUIRED_CLAIMS = ["sub", "exp", "iss", "role", "tenant_id"]
def validate_claims(f):
def wrapper(*args, **kwargs):
auth = request.headers.get("Authorization", "")
token = auth[7:] if auth.startswith("Bearer ") else None
try:
payload = jwt.decode(
token,
SECRET,
algorithms=["HS256"],
options={"require": REQUIRED_CLAIMS}
)
# Validate custom claims
if payload["role"] not in ["admin", "analyst", "viewer"]:
return jsonify({"error": "Invalid role"}), 403
# Check specific permissions
required_scope = request.headers.get("X-Required-Scope")
if required_scope:
permissions = payload.get("permissions", [])
if required_scope not in permissions:
return jsonify({
"error": "Insufficient permissions",
"required": required_scope
}), 403
g.user = payload
except jwt.MissingRequiredClaimError as e:
return jsonify({
"error": f"Missing required claim: {e}"
}), 401
except jwt.InvalidTokenError:
return jsonify({"error": "Invalid token"}), 401
return f(*args, **kwargs)
return wrapper
@app.route("/api/admin")
@validate_claims
def admin_only():
if g.user.get("role") != "admin":
return jsonify({"error": "Admin only"}), 403
return jsonify({"message": "Admin access granted"})
Claim Design Best Practices
| Do | Don't |
|---|---|
| Keep claims minimal | Include unnecessary data |
Use sub for user identity |
Use custom claims for identity |
Prefix custom claims (e.g., app_) |
Use names that may conflict |
| Validate all required claims | Assume claims are present |
| Document all custom claims | Keep claims undocumented |
Common Mistakes
1. Putting Sensitive Data in Claims
The payload is base64-encoded, not encrypted. Never include passwords, credit cards, or secrets.
2. Not Validating Required Claims
If your code assumes role is always present, a token without role may bypass authorization checks.
3. Using Reserved Names for Custom Claims
Creating a custom claim named sub or exp overrides the standard. Use unique prefixes.
4. Making Claims Too Granular
A permissions array with 100+ items bloats the token. Group into roles.
5. Not Including iat
Without iat, you cannot determine when a token was issued, making some security analyses impossible.
Practice Questions
- What are the three types of JWT claims?
- What claim identifies the user in a JWT?
- Why should custom claims be prefixed?
- What is the difference between
nbfandexp? - How do you handle missing required claims?
Answers:
- Registered (standardized, e.g.,
iss,sub,exp), Public (IANA-registered or URL-namespaced), Private (custom between parties). - The
sub(subject) claim contains the user identifier. It is the primary identity claim. - To avoid conflicts with future registered claims or other applications' custom claims. Example:
app_roleinstead ofrole. nbf(Not Before) sets when the token becomes valid.exp(Expiration) sets when it becomes invalid. Both are optional timestamps.- Use the
requireoption injwt.decode()or explicitly check each claim after decoding. Return 401 for missing required claims.
Challenge: Design a claims schema for a multi-tenant document management API. Define registered, public, and private claims. Show how different roles (admin, editor, viewer) have different permission claims.
FAQ
Mini Project
Create a Python script that issues JWTs with a well-designed claims schema (registered + custom claims), then write authorization middleware that validates specific claims and enforces permissions based on the role claim.
What's Next
Now learn about JWT Middleware — building reusable JWT validation components for your API framework.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro