Skip to content

JWT Claims — Standard and Custom Claims for User Identity and Authorization

DodaTech Updated 2026-06-28 5 min read

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

  1. What are the three types of JWT claims?
  2. What claim identifies the user in a JWT?
  3. Why should custom claims be prefixed?
  4. What is the difference between nbf and exp?
  5. How do you handle missing required claims?

Answers:

  1. Registered (standardized, e.g., iss, sub, exp), Public (IANA-registered or URL-namespaced), Private (custom between parties).
  2. The sub (subject) claim contains the user identifier. It is the primary identity claim.
  3. To avoid conflicts with future registered claims or other applications' custom claims. Example: app_role instead of role.
  4. nbf (Not Before) sets when the token becomes valid. exp (Expiration) sets when it becomes invalid. Both are optional timestamps.
  5. Use the require option in jwt.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

What is the maximum number of claims?

There is no limit, but larger tokens increase request size. Aim for under 1KB total (about 10-15 claims with typical values).

Can I update claims without re-issuing?

No. Claims are fixed at issuance. To update, issue a new token with the updated claims.

What claims should every JWT include?

At minimum: sub (user), iss (issuer), iat (issued at), exp (expiration). Recommended: jti (unique ID), aud (audience).

How do I invalidate claims?

You cannot invalidate claims in a stateless JWT. Use short TTL and issue a new token with updated claims, or use a blocklist for immediate changes.

Do I need all registered claims?

Only the ones relevant to your use case. exp is mandatory for security. iss and aud are essential for multi-service systems.

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