Wildcard Matching
The * wildcard can be used in either the resource or action position of a permission string. Wildcards in a granted permission match any value in the corresponding position of the requested permission.
How Matching Works
When a user's permission is checked, the engine iterates through all permissions the user holds (from all their roles) and returns true as soon as any one of them matches. No permission can explicitly deny — the model is additive.
The matching logic is implemented in AuthorizationService::matchesWildcard():
- Exact match —
posts:creatematchesposts:create - Wildcard action —
posts:*matches any action onposts(e.g.,posts:create,posts:delete) - Wildcard resource —
*:readmatches thereadaction on any resource (e.g.,posts:read,users:read) - Wildcard all —
*:*matches any permission (full access)
No other wildcard patterns are supported. The * is only meaningful in the resource or action slot, not as a partial glob (e.g., post*:create is not a valid pattern and will not match).
Pattern Reference
| Granted permission | Requested permission | Match? |
|---|---|---|
posts:create | posts:create | Yes — exact match |
posts:create | posts:delete | No |
posts:* | posts:create | Yes — wildcard action |
posts:* | posts:delete | Yes — wildcard action |
posts:* | users:read | No — resource differs |
*:read | posts:read | Yes — wildcard resource |
*:read | users:read | Yes — wildcard resource |
*:read | posts:delete | No — action differs |
*:* | posts:create | Yes — wildcard all |
*:* | anything:goes | Yes — wildcard all |
*:* | reports:export | Yes — wildcard all |
Use Cases
Full Admin Access
Assign *:* to your admin role to grant complete access across all resources and actions:
{
"name": "admin",
"display_name": "Administrator",
"permissions": ["*:*"]
}
Read-Only Role
Assign *:read to grant read access to every resource in your application:
{
"name": "viewer",
"display_name": "Viewer",
"permissions": ["*:read"]
}
Resource-Scoped Full Access
Grant a role full control over a specific resource while leaving other resources untouched:
{
"name": "billing_manager",
"display_name": "Billing Manager",
"permissions": ["billing:*"]
}
Mixed Exact and Wildcard Permissions
Roles can combine exact permissions and wildcard permissions freely:
{
"name": "content_lead",
"display_name": "Content Lead",
"permissions": ["posts:*", "comments:moderate", "users:read"]
}
A user with this role can perform any action on posts, can moderate comments, and can read users. They cannot, for example, delete users.
Multiple Roles
When a user holds multiple roles, all permissions from all roles are combined. A user is granted access if any role in their set contains a matching permission.
Example:
User user-123 has two roles:
viewerwith permission*:readpost_editorwith permissionsposts:create,posts:update
Permission check results:
posts:read→true(matched by*:read)posts:create→true(matched byposts:create)posts:delete→false(no matching permission)users:read→true(matched by*:read)users:delete→false(no matching permission)
Team-Inherited Permissions
Permissions inherited through team membership follow the same wildcard matching rules. If a team is assigned a role with *:read, all members of that team can read any resource — the matching logic is identical.
Validation
When creating or updating a role, all permission strings are validated against the pattern:
^[a-zA-Z0-9_*-]+:[a-zA-Z0-9_*-]+$
This allows letters, digits, underscores, hyphens, and * in each position. Strings that do not contain exactly one colon or contain characters outside this set are rejected with a 422 error.
* is only meaningful in a full-position wildcard. post*:create is a valid string according to the pattern, but it will not act as a glob — it will only match the literal permission post*:create. Use posts:create for exact matching or posts:* for full resource coverage.