Skip to main content

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():

  1. Exact matchposts:create matches posts:create
  2. Wildcard actionposts:* matches any action on posts (e.g., posts:create, posts:delete)
  3. Wildcard resource*:read matches the read action on any resource (e.g., posts:read, users:read)
  4. 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 permissionRequested permissionMatch?
posts:createposts:createYes — exact match
posts:createposts:deleteNo
posts:*posts:createYes — wildcard action
posts:*posts:deleteYes — wildcard action
posts:*users:readNo — resource differs
*:readposts:readYes — wildcard resource
*:readusers:readYes — wildcard resource
*:readposts:deleteNo — action differs
*:*posts:createYes — wildcard all
*:*anything:goesYes — wildcard all
*:*reports:exportYes — wildcard all

Use Cases

Full Admin Access

Assign *:* to your admin role to grant complete access across all resources and actions:

json
{
  "name": "admin",
  "display_name": "Administrator",
  "permissions": ["*:*"]
}

Read-Only Role

Assign *:read to grant read access to every resource in your application:

json
{
  "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:

json
{
  "name": "billing_manager",
  "display_name": "Billing Manager",
  "permissions": ["billing:*"]
}

Mixed Exact and Wildcard Permissions

Roles can combine exact permissions and wildcard permissions freely:

json
{
  "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:

  • viewer with permission *:read
  • post_editor with permissions posts:create, posts:update

Permission check results:

  • posts:readtrue (matched by *:read)
  • posts:createtrue (matched by posts:create)
  • posts:deletefalse (no matching permission)
  • users:readtrue (matched by *:read)
  • users:deletefalse (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:

text
^[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.