Check permissions by URL with regex URL mapping
Check whether a user can call an HTTP URL by matching the URL against a regular expression (regex) mapping rule. This page is for developers who enforce permissions at the API layer and have URLs that a simple URL template can't describe, such as optional segments, varying subdomains, or query parameters in any order.
How regex URL mapping works
A regex mapping rule links an HTTP method and a regex URL pattern to a Permit resource and action. When the policy decision point (PDP) receives a URL check:
- The PDP finds the rules with the same HTTP method (case-insensitive) whose pattern matches the request URL, starting from the first character.
- When several rules match, the PDP uses the rule with the highest priority.
- The PDP passes the values of the pattern's named capture groups to the check as resource attributes.
- The PDP checks whether the user can perform the rule's action on the rule's resource in the given tenant, and returns the decision.
Use regex rules to extract several values from paths and query strings, to match optional components, subdomains, or both http and https, and to cover many URL shapes with one rule.
Named capture groups pass URL values to your policy
The PDP evaluates patterns with Python regex syntax. A named capture group, (?P<param_name>pattern), captures part of the URL. The PDP passes each captured value as a resource attribute named param_name, so attribute-based access control (ABAC) rules in your policy can use the value.
Prerequisites
- A Permit.io policy with the resources and actions your URLs map to
- Your environment API key, and your project and environment IDs or keys (Get your API key)
- Docker, to run a container PDP version 0.8.0 or later
1. Create a regex mapping rule with the API
Create regex mapping rules with the proxy configs endpoint of the Permit API. Replace {project_id} and {env_id} with your project and environment, and YOUR_PERMIT_KEY with your API key. This request creates a rule that maps POST requests for a user profile URL to the update action on the users resource:
curl --location 'https://api.permit.io/v2/facts/{project_id}/{env_id}/proxy_configs' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer YOUR_PERMIT_KEY' \
--data '{
"name": "Users API",
"key": "users_api",
"mapping_rules": [
{
"url": "^https://api\\.example\\.com/api/v1/users/(?P<user_id>[0-9]+)/profile$",
"http_method": "post",
"resource": "users",
"action": "update",
"url_type": "regex"
}
],
"secret":"abc123notUsed"
}'
In this rule:
url_typeis"regex". Withouturl_type, the PDP treatsurlas a simple URL template.(?P<user_id>[0-9]+)captures a numeric user ID from the path. The PDP passes the value as theuser_idresource attribute.secretis required by the proxy configs API. The PDP's URL check doesn't use thesecretvalue.
The pattern is a JSON string, so every regex backslash needs a second backslash: \. becomes \\.. A single backslash before . is an invalid JSON escape, and the API rejects the request body.
2. Run the PDP
URL checks run on a container PDP. Start one with your environment API key:
docker run -it \
-p 7766:7000 \
--env PDP_API_KEY=permit_key_{your_permit_env_key} \
--env PDP_DEBUG=True \
permitio/pdp-v2:latest
Regex URL mapping requires PDP version 0.8.0 or later.
3. Check a URL
Send the user, the HTTP method, the full request URL, and the tenant to the PDP's POST /allowed_url endpoint. This example assumes the PDP runs locally on port 7766. The url must match the rule's pattern. For the rule in step 1, a matching URL is https://api.example.com/api/v1/users/123/profile.
curl --location 'http://localhost:7766/allowed_url' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer permit_key_{your_key}' \
--data '{
"user": {
"key": "raz@permit.io"
},
"url": "https://api.example.com/api/v1/users/123/profile",
"http_method": "post",
"tenant": "default"
}'
In Java, call permit.checkUrl(user, httpMethod, url, tenant) instead. The other SDKs don't have a URL check function, so call the /allowed_url endpoint over HTTP.
Verify the URL check result
The response contains an allow field:
"allow": truemeans a rule matched and the user can perform the mapped action on the mapped resource."allow": falsewith adebug.reasonofMatched mapping rule not found for the requested URL and HTTP methodmeans no pattern matches the URL and method. Test the pattern against the full URL, including scheme and host, and compare the HTTP method."allow": falsewithout that reason means a rule matched, and the policy denies the action.
Common regex patterns
Each of the following patterns is JSON-escaped, ready for the url field of a mapping rule. The tables explain the unescaped pattern.
Match a numeric path parameter
//JSON Escaped Regex URL
"url": "^https://api\\.example\\.com/users/(?P<user_id>[0-9]+)/profile$"
This pattern matches URLs with a numeric user ID in the path:
https://api.example.com/users/123/profile
https://api.example.com/users/456/profile
| Pattern Part | Description |
|---|---|
^ | Matches the start of the string |
https:// | Matches the protocol literally |
api\.example\.com | Matches the domain (dots are escaped with backslashes) |
/users/ | Matches the literal path segment |
(?P<user_id>[0-9]+) | Named capture group: - ?P<user_id>: Names the captured value "user_id"- [0-9]+: Matches one or more digits |
/profile | Matches the literal path segment |
$ | Matches the end of the string |
Use this pattern for REST endpoints with a numeric ID in a fixed path.
Match an optional path segment
//JSON Escaped Regex URL
"url": "^https://api\\.example\\.com/api/users(?:/(?P<user_id>[0-9]+))?$"
This pattern matches URLs with or without a user ID:
https://api.example.com/api/users
https://api.example.com/api/users/123
| Pattern Part | Description |
|---|---|
^ | Matches the start of the string |
https:// | Matches the protocol literally |
api\.example\.com | Matches the domain (dots are escaped with backslashes) |
/api/users | Matches the literal path segments |
(?:...) | Non-capturing group (used for grouping without capturing) |
(?P<user_id>[0-9]+) | Named capture group: - ?P<user_id>: Names the captured value "user_id"- [0-9]+: Matches one or more digits |
? | Makes the entire group optional |
$ | Matches the end of the string |
Use this pattern when one rule covers a list endpoint and its detail endpoint.
Match query parameters in any order
//JSON Escaped Regex URL
"url": "^https://api\\.example\\.com/search\\?(?=.*q=(?P<query>[^&]+))(?=.*page=(?P<page>[0-9]+)).*$"
This pattern matches URLs with both query parameters, in any order:
https://api.example.com/search?q=test&page=1
https://api.example.com/search?page=2&q=example
| Pattern Part | Description |
|---|---|
^ | Matches the start of the string |
https:// | Matches the protocol literally |
api\.example\.com | Matches the domain (dots are escaped with backslashes) |
/search | Matches the literal path segment |
\? | Matches the literal question mark (escaped) |
(?=...) | Positive lookahead (ensures something exists without consuming it) |
.* | Matches any characters |
[^&]+ | Matches one or more characters that aren't ampersands (for query values) |
$ | Matches the end of the string |
Use this pattern for search endpoints where clients send parameters in any order. Each lookahead captures one parameter as an attribute: query and page.
Match any subdomain
//JSON Escaped Regex URL
"url": "^https://[\\w-]+\\.example\\.com/api/v1/users/(?P<user_id>[0-9]+)$"
This pattern matches URLs on any subdomain:
https://api.example.com/api/v1/users/123
https://staging.example.com/api/v1/users/456
https://dev-api.example.com/api/v1/users/789
| Pattern Part | Description |
|---|---|
^ | Matches the start of the string |
https:// | Matches the protocol literally |
[\w-]+ | Matches one or more word characters or hyphens: - \w: matches letters, numbers, and underscores- -: allows hyphens in subdomains |
\.example\.com | Matches the domain (dots are escaped with backslashes) |
/api/v1/users/ | Matches the literal path segments |
(?P<user_id>[0-9]+) | Named capture group: - ?P<user_id>: Names the captured value "user_id"- [0-9]+: Matches one or more digits |
$ | Matches the end of the string |
Use this pattern when the same API runs on several subdomains, such as staging and dev.
Match a user ID anywhere in the path
//JSON Escaped Regex URL
"url": "^https?:\\/\\/[^\\/]+(?:.*?\\/users(?:\\/(?P<user_id>[^\\/]+))?(?:\\/.*)?)?$"
This pattern matches any URL with a users segment, and captures the segment after users as user_id when present. It matches:
https://api.example.com/users/123
https://api.example.com/v1/users/123
https://api.example.com/v1/users/123/profile
https://api.example.com/v1/users/123/settings/notifications
| Pattern Part | Description |
|---|---|
^ | Matches the start of the string |
https? | Matches "http" or "https" (the 's' is optional) |
:\/\/ | Matches the literal "://" (forward slashes are escaped) |
[^\/]+ | Matches the domain (any characters except forward slashes) |
.*? | Non-greedy match of any characters |
(?:\/.*)? | Optional trailing path segments |
$ | Matches the end of the string |
A broad pattern like this one can match URLs you didn't intend. Give narrower rules a higher priority so the PDP picks them first.
Match HTTP and HTTPS
//JSON Escaped Regex URL
"url": "^https?:\\/\\/api\\.example\\.com\\/users\\/(?P<user_id>[0-9]+)\\/profile$"
This pattern matches both HTTP and HTTPS URLs:
http://api.example.com/users/123/profile
https://api.example.com/users/123/profile
| Pattern Part | Description |
|---|---|
^ | Matches the start of the string |
https? | Matches "http" or "https" (the 's' is optional) |
:\/\/ | Matches the literal "://" (forward slashes are escaped) |
api\.example\.com | Matches the domain (dots are escaped) |
/users/ | Matches the literal path segment |
(?P<user_id>[0-9]+) | Named capture group: - ?P<user_id>: Names the captured value "user_id"- [0-9]+: Matches one or more digits |
/profile | Matches the literal path segment |
$ | Matches the end of the string |
Use this pattern when your API accepts both http and https, for example in development.
Match any domain
//JSON Escaped Regex URL
"url": "^https:\\/\\/[^\\/]+\\/api\\/v1\\/users\\/(?P<user_id>[0-9]+)\\/profile$"
This pattern matches any domain name:
https://api.example.com/api/v1/users/123/profile
https://api.staging.example.com/api/v1/users/123/profile
https://api.production.example.com/api/v1/users/123/profile
| Pattern Part | Description |
|---|---|
^ | Matches the start of the string |
https:\/\/ | Matches the literal "https://" (forward slashes are escaped) |
[^\/]+ | Matches the domain (any characters except forward slashes) |
/api/v1/ | Matches the literal API version path |
/users/ | Matches the literal path segment |
(?P<user_id>[0-9]+) | Named capture group: - ?P<user_id>: Names the captured value "user_id"- [0-9]+: Matches one or more digits |
/profile | Matches the literal path segment |
$ | Matches the end of the string |
Use this pattern when the same API runs on several domains, so one rule applies on every domain.
Best practices for regex patterns
- Escape special characters. Escape dots in domains (
\.), and escape each backslash again in the JSON body. - Anchor patterns. Start with
^and end with$. The PDP matches from the start of the URL, and without$a pattern also matches longer URLs. - Use named groups. Only named capture groups become resource attributes.
- Test patterns before production. Test each pattern against real request URLs, including URLs that must not match.
- Keep patterns narrow. Narrow patterns are easier to read and less likely to match unintended URLs.