Route.Allow or a service-wide Allow.
Access layers
The first three layers are configured by the Kernel configuration. The last
layer is declared by the service when it registers a route. See Routes and
transports for how a service registers routes and which
part of the declaration carries the access policy.
User identity
For HTTP requests, the Kernel checks thearupa-auth session cookie first and
then the Authorization header. A valid session resolves to a username. The
Kernel looks up that username in Groups and creates a host-verified identity
containing:
- the username;
- the groups that contain that username; and
- the
Authenticatedstate.
Policy semantics
All access checks use the same policy model:- An empty policy is public. It allows unauthenticated users.
RequireAuth = truewith no groups allows any authenticated user.- A non-empty
Groupslist requires authentication and membership in at least one listed group. - A user who is authenticated but belongs to none of the listed groups is forbidden.
Groups = ["root", "staff"]
allows a user in root or staff; the user does not need to belong to both
groups.
The two rejection states are different:
- An unauthenticated request is an authentication failure and produces
401 Unauthorized. - An authenticated user who does not satisfy the group requirement is an
authorization failure and produces
403 Forbidden.
Denying access
Because an emptyGroups list is public, there’s no way to write “deny
everyone” by leaving Groups empty — an empty list and an absent policy
behave the same way. To make a policy reject every request instead, name a
group that has no members, for example Groups = ["nobody"]. Membership is
determined entirely by the Groups table’s own membership lists, so a
group name is never validated against anything when it’s used in a policy;
nobody doesn’t need an entry in [Groups] at all. An undefined group
simply has no members, which no user can ever belong to, so this works as a
deny-all placeholder without requiring any bookkeeping elsewhere in the
configuration. This applies to every layer on this page — Route.Allow, a
service’s Allow, and a route’s own Access policy all resolve group
membership the same way.
Host route access
Route.Allow protects HTTP paths before the request reaches either a host
handler or a service handler:
Route.Allow map uses the Kernel’s shared path-pattern implementation. A
normal path is an exact match, while a pattern ending in / matches that path
subtree. The /* is not a valid configuration; it doesn’t support wildcard.
A key can also be method-qualified by prefixing the path with an HTTP method
and a colon, as in "GET:/api/list" above. A method-qualified rule only
applies to requests using that method; a plain path with no METHOD: prefix
applies to every method. A rule for GET also matches HEAD requests, since
the two share caching semantics. The
method must be an uppercase HTTP token — "get:/api/list" is invalid, and so
is a colon with nothing in front of it, such as ":/api/list". Every key in
Route.Allow is validated together: if any one of them is malformed, the
whole update is rejected and none of the submitted changes are saved.
When multiple Route.Allow patterns match a request, the longest matching
path wins first. If more than one matching rule shares that same longest
path — a method-qualified rule and a plain rule, or two rules for different
methods — the more specific method wins: an exact method match beats a GET
rule matching a HEAD request, which beats a plain, method-less rule. The
Kernel does not combine groups from multiple matching rules; exactly one
rule’s group list is evaluated. See Route for the complete
path-pattern rules, root-path behavior, and validation details.
If no rule matches, Route.Allow does not restrict the request. If a matching
rule has an empty group list, its policy is also effectively public because an
empty access policy allows everyone; an empty list does not mean “deny all”.
See Denying access above for how to write a rule that
actually rejects every request.
Route.Allow is evaluated for every HTTP request by the outer auth middleware.
When it rejects a request, it returns the normal JSON 401 or 403 response.
It does not use the configured Pages redirect. See Pages
for browser redirects used by service-protected resources.
Service-wide access
Allow under [Services.<name>] applies to the service as a whole:
Allow list leaves the service open at this layer. A non-empty list
requires the user to belong to at least one of the listed groups.
[Services.default] provides the base service configuration. A non-empty
Allow list in [Services.<name>] replaces the default group list for that
service. If the per-service list is omitted or empty, the current implementation
keeps the default list.
The service policy is refreshed when the Kernel reloads its configuration, so a
loaded service uses the updated group list for subsequent requests and events.
Route access
A service can add another policy when it registers a route. This policy is not written inconfig.toml; it is part of the route declaration the service
sends to the Kernel, and it applies the same way no matter which transport the
route is bound to.
An HTTP route carries its own Access policy. The Kernel checks the
service-wide policy first and the route policy second:
Access policy, checked
the same way as a route handled dynamically by the service.
Because every check must pass, the effective permission is the intersection of
the applicable layers. For example, a user must be in both staff from the
service’s Allow and operators from a route’s Access.Groups to reach that
route.
Example
With the following configuration:admin user can pass the /api/service/ host-route rule because they are
in root. alice is authenticated but receives 403 Forbidden for that
path because they are not in root. An unauthenticated request receives
401 Unauthorized.
The same admin user cannot access any route protected by the hello
service’s Allow = ["staff"] because admin is not in staff. Being
authenticated is not enough when a policy names groups.