JWT permissions: scopes, roles, and how to encode them

Resource:action strings, OAuth scopes, bitmasks — which permission encoding to use in a multi-tenant JWT, and the trade-offs of each.

shield

Private: Decoding, analysis, signature verification, and token generation all run in your browser. Nothing is sent to any server, except a JWKS URL you explicitly enter and click Verify on.

At a glance

Most common (B2B SaaS)
resource:action strings
Most common (OAuth APIs)
space-delimited scopes
Smallest tokens
numeric bitmask (10–50× smaller)
Most flexible
resource:action with wildcards (use sparingly)

There are roughly four ways to encode permissions in a JWT: resource:action strings (`billing:read`, `users:write`), space-delimited OAuth scopes (`read:posts write:comments`), flat role names (`admin`, `member`), and numeric bitmasks. Each has trade-offs around readability, evolvability, and token size. Here's how to pick.

Resource:action — the SaaS default

The pattern `resource:action` (e.g. `billing:read`, `users:write`) is the most common in B2B SaaS for a reason: it reads like English, it groups naturally by resource, and it composes well. You can write `if (token.permissions.includes("billing:write"))` and the intent is obvious.

It scales to a few hundred permissions before token size becomes uncomfortable. Past that, group permissions into roles and put the role in the token instead of the full set; expand server-side as needed.

OAuth scopes — for APIs you expose

OAuth 2.0 prescribes space-delimited scope strings (`scope: "read:posts write:comments"`). Use this convention if you're building an API that third parties will call — it interoperates with every OAuth client library out there.

For internal multi-tenant apps where you control both ends, resource:action arrays are easier to work with. There's no rule against using both: third-party tokens carry OAuth scopes, internal tokens carry resource:action arrays.

Frequently asked questions

When should I use bitmasks?expand_more
When token size is a critical constraint (e.g., over a slow mobile network, or when tokens are passed in URLs). Bitmasks are 10–50× more compact than string permissions but require a permission map both sides agree on, plus careful handling when adding new permissions (each new bit is a one-way migration). For most apps, string permissions are clearer and the size difference doesn't matter.
Should permissions live in the token or be looked up server-side?expand_more
Both. Put scoped permissions in the token for fast authorization checks on hot paths. Keep the master permission list in your database for management and audit. The JWT is a snapshot — it reflects permissions at issue time, and a stale token may not see permission changes until refresh.
Are wildcard permissions safe?expand_more
They're convenient and routinely become a security problem. A `*:*` permission is fine when one user has it and that user is the founder; a `*:*` permission held by 30 employees who joined over five years is a different story. Audit them quarterly. The analyzer flags wildcards in the Findings panel.

Related guides

Related Tools