Permissions
Control over atproto resources is described and granted using "permissions". A group of a permissions related to a specific Lexicon namespace (record types and API endpoints) can be bundled together as a "permission set". Both are used in the context of OAuth to grant client software access to account resources on a PDS. For example, the ability to write records of specific types to the user's public repository, or make authenticated API requests to remote services. Developers declare the permissions their app requires to function, and end users are shown the permissions when granting access to the app.
Each resource type has a defined set of parameters that can attenuate the permission. A permission can be represented in a string format (for direct use as an OAuth scope), or as a JSON object (for use in permission sets). Permission sets are published as public lexicon schemas, and are constrained in scope to named resources under the same NSID hierarchy as the NSID of the permission set itself.
Resource Types
Permissions relate to user owned resources on PDS instances:
repo: Public Repository (records and collections)rpc: Service Authentication (API calls to external services)blob: uploaded media filesidentity: DID and handleaccount: hosting status, email address
There is an additional "meta" resource, include, which is used to reference a permission set (see Permission Sets section below).
This section describes these resources in more depth, including all the parameters that can constrain the permission. For each resource, one parameter may be marked as "positional", which impacts the string representation syntax (described in a later section).
Resources and permissions are described in the abstract, not tied to specific PDS API endpoints. For example, rpc permissions describe the ability to make remote service API calls using inter-service authentication. This includes both proxied PDS requests (using the Atproto-Proxy HTTP header) and fetching service auth tokens using com.atproto.server.getServiceAuth.
repo
Write access to records in the account's public repository. Can be limited to specific record types (collections) or actions (eg update vs delete).
Parameters:
collection(array of strings, required, positional): NSID of record types. Wildcard (*) is allowed in scope string syntax, and grants access to all records. Partial wildcards are not supported (eg, can not usecom.example.*). Wildcards are not supported in permissions within a permission set.action(array of unique strings, optional): defines the set of record operations allowed. Allowed values arecreate,update,delete. If not defined, all operations are allowed
Examples:
# full permission (create, update, delete) on a single record type
repo:app.example.profile
{
"type": "permission",
"resource": "repo",
"collection": ["app.example.profile"]
}
# same as above, with actions being explicit
repo:app.example.profile?action=create&action=update&action=delete
{
"type": "permission",
"resource": "repo",
"collection": ["app.example.profile"],
"action": ["create", "update", "delete"]
}
# full permissions on multiple record types
repo?collection=app.example.profile&collection=app.example.post
{
"type": "permission",
"resource": "repo",
"collection": ["app.example.profile", "app.example.post"]
}
# full permissions on all record types (not allowed within permission set)
repo:*
# delete only permission on all record types (not allowed within permission set)
repo:*?action=delete
rpc
The ability to make authenticated API requests to remote services. This includes both requesting service auth tokens (JWTs) using the com.atproto.server.getServiceAuth endpoint on the PDS, and requests proxied via the PDS.
The permission is parameterized by the remote endpoint (lxm, short for "Lexicon Method") and the identity of the remote service (the audience, aud). Permissions must be restricted by at least one of these parameter. In other words, the ability to call any API endpoint on any service can not be declared with a single permission.
When included in a permission set, the audience parameter may be inherited from the overall set.
Parameters:
lxm(array of strings, required, positional): NSID of API endpoints. Wildcard (*) is allowed in scope string syntax, and gives access to all endpoints. Partial wildcards are not supported (eg, can not usecom.example.*). Wildcards are not supported in permissions within a permission set.aud(string, semi-required): audience of API requests, as a DID service reference: DID followed by required service type fragment, egdid:web:api.example.com#srvtype). Supports wildcard (*), though as noted aboveaudandlxmcan not both be wildcard. DID references are not allowed in permission set context. Always required in granular string representation; contingent oninheritAudin permission sets.inheritAud(boolean, optional): only used inside permission sets. If true, anaudvalue will be inherited from theinclude:invocation, and theaudfield is not required on the permission.
If inheritAud is true and the permission also defines aud, then the permission is invalid (and should be ignored). If inheritAud is true and no aud is defined on the invoking include:, then the rpc permission is invalid (and should be ignored).
Examples:
# Submit moderation reports to any service
rpc:app.example.moderation.createReport?aud=*
{
"type": "permission",
"resource": "rpc",
"lxm": ["app.example.moderation.createReport"],
"aud": "*"
}
# Call any RPC method on a specific service (not allowed within permission set)
rpc?lxm=*&aud=did:web:api.example.com%23svc_appview
blob
Ability to upload media files (blobs) to PDS.
Permissions of this type can not be included in permission sets, and must be requested directly by client apps.
Parameters:
accept(array of strings, required, positional): MIME types or partial MIME type glob patterns (*/*ortext/*for example). Same syntax as theacceptfield in thebloblexicon type.
Examples:
# Upload any type of blob
blob:*/*
# Upload video or html
blob?accept=video/*&accept=text/html
account
Control of PDS account hosting details, such as (private) account email.
Permissions of this type can not be included in permission sets, and must be requested directly by client apps.
Parameters:
attr(string, required, positional): a component of account configuration. Wildcard is not supported.action(string, optional): degree of control. Currently supportsreadormanage. If not specified, default isread.
Attributes:
email: account email address. Thereadaction makes the email and verification status visible. Themanageaction includesread, and also allows changing the email address.repo: ability to update entire public repository using a CAR file. Themanageaction allows importing entire CAR files, for example during account migration. Thereadaction does nothing.
Examples:
# read account email
account:email
# Import repo
account:repo?action=manage
identity
Control over network identity, meaning the account DID document and handle. Note that the PDS might not be able to facilitate identity changes if it does not have control over the DID document (for example, when the account uses did:web).
Permissions of this type can not be included in permission sets, and must be requested directly by client apps.
Parameters:
attr(string, required, positional): an aspect or component of account. May be be wildcard (*), indicating full control of DID document and handle.
Attributes:
handle: ability to update handle. This includes the registration of the handle in the DID document, as well as any domain names controlled by the PDS.*(wildcard): full control over DID document and handle.
Examples:
# Update account handle
identity:handle
Scope String Syntax
Permissions need to be represented as simple strings when they are requested directly as OAuth scopes. This section describes the syntax for permission scope strings.
The strings are case-sensitive and use a subset of printable non-whitespace ASCII characters. There is a resource name part, followed by an optional "positional" part separated by a colon (:), followed by optional parameters starting with a question mark (?). The positional part can contain any characters except for a question mark, including additional colon characters, hash (#), period, etc. and support being percent encoded. Parameters are separated by ampersands (&), have name and value parts separated by equals (=), and support percent-encoding of reserved characters. Array values can be represented by repeated parameters with the same name part.
For example, the scope string account:repo?action=manage has a resource name account, a "positional" part with the value repo, and a key/value pair action=manage. Knowing that the positional parameter for account is attr, the same permission could be represented as account?action=manage&attr=repo.
If a parameter is specified in the positional position, it can not also be described in the key/value parameters. Eg, repo:com.example.record?collection=com.example.other is not allowed.
Examples of syntactically and semantically valid scope strings:
identity:*
identity:*?
rpc?lxm=*&aud=did:web:api.example.com%23svc_appview
blob?accept=video/*&accept=text/html
repo:app.example.profile?action=create&action=update&action=delete
include:app.example.authFull?aud=did:web:api.example.com%23svc_chat
Scope strings that match the general syntax, but do not have valid semantics under the current resource definitions:
resource
resource:positional?key=val
resource:positional&thing?key=val
service:did:web:com.example#type?key=val
resource:
resource:?
resource:&
resource?
Examples which do not match the general syntax:
resource:positional?key=québec
emoji:☺️
Permission Sets
Full-featured client apps require a large number of granular permissions to function: dozens or even hundreds of individual permissions. This presents a user experience and security challenge, as long lists of permissions are unlikely to be reviewed carefully, and a developer experience issue, as defining and maintaining these lists is toilsome.
To simplify permission management, Lexicon designers can define "sets" of permissions as part of the schemas they publish. These permission sets are themselves Lexicon schemas and are referred to by NSID, such as com.example.authBasicFeatures.
Authorization Servers resolve, authenticate, and process permission-sets dynamically. Sets include user-meaningful titles and summaries that are displayed to end users during the authorization request flow, with support for internationalization. These summaries improve consent comprehension and ultimately account security (the user interface should also make it possible to expand the specific granular permissions being granted). Permission sets are published publicly and can be used by any client developer. (Caching and fallback behaviors for dynamic resolution are discussed below.)
The below is an example permission set Lexicon. The full syntax is described in the Lexicon specification.
{
"lexicon": 1,
"id": "com.example.authBasicFeatures",
"defs": {
"main": {
"type": "permission-set",
"title": "Basic App Functionality",
"title:langs": {
"ja": "基本的なアプリ機能"
},
"detail": "Creation of posts and interactions",
"detail:langs": {
"ja": "投稿と交流の作成"
},
"permissions": [
{
"type": "permission",
"resource": "repo",
"collection": ["app.example.post"]
},
{
"type": "permission",
"resource": "repo",
"collection": ["app.example.like"],
"action": ["delete"]
},
{
"type": "permission",
"resource": "rpc",
"inheritAud": true,
"lxm": [
"app.example.getFeed"
"app.example.getProfile",
"app.example.getPreferences",
"app.example.putPreferences",
"app.example.getAuthorFeed",
...
]
},
{
"type": "permission",
"resource": "rpc",
"aud": "*",
"lxm": ["app.example.getFeedSkeleton"]
}
]
}
}
}
This entire set of permissions could be requested with an auth scope string like:
include:com.example.authBasicFeatures?aud=did:web:api.example.com%23svc_appview
Because some of the rpc permissions have the inheritAud flag set to true, the aud parameter on the include will be passed down to those specific rpc permissions. If the aud had not been part of the include, the default value (no audience) would have been used instead.
Permission sets are Lexicon schemas and are published and fetched using the Lexicon resolution system, which includes cryptographic authentication. Permission sets can be updated over time as new schemas are added to a namespace (eg, new record types or API endpoints). Authorization Servers are expected to maintain a cache of resolved sets, but to re-resolve them periodically. The permissions associated with an Access Token should remain fixed, but when a client refreshes their tokens (obtaining a new access token), the computed permissions for the session may be updated to reflect changes to sets requested by the client.
This adds an intentional degree of temporal dynamism to OAuth sessions involving permission sets. Lexicon designers can define new resources (eg, record types) and update published sets to include permissions to those resources. Client software can then be updated to take advantage of those new lexicons, without requiring users to re-authenticate their sessions. OAuth session are strictly limited to the auth scopes in the initial grant; it is the indirection of permission sets which enables this flexibility.
Authorization Servers and Resource Servers must ignore any individual permission declarations within a permission set that describe an unknown resource, or include unexpected parameter names or values. The reason for this behavior is that the permission system is expected to evolve over time, and new client permission requests should not be rejected in whole. At the same time, new parameters (fields) might attentuate permissions further, and it would be unsafe to grant partially-understood permissions.
Namespace Authority
Permission sets are limited to expressing permissions that reference resources under the same NSID namespace as the set itself.
Authority is based on the relative structure of NSIDs, without "siblings" or special namespaces. Broadly, this ensures that sets can not request permissions across namespaces. Specifically:
- Permission sets can address resources in the same NSID "group"; or "children" (sub-domains), recursively deep
- can not address "sibling groups" or "parents" in the NSID hierarchy
For example, the set app.example.feed.authOnlyPost could include permissions to app.example.feed.post records and making app.example.feed.getPostThread API endpoint requests to remote services. But it could not grant permissions to app.example.actor.profile. A permission set app.example.authFull, which is a level up in the hierarchy, could include permissions to all these resources, or even further down the hierarchy.
Resolution and Caching
Permission set Lexicons need to be resolved by Authorization Servers (eg, PDS instances). The existing Lexicon publication and resolution system describes how schemas are published and verified.
To reduce network traffic and increase resiliency to outages, Authorization Servers are expected to cache permission set resolution. Caches may be shared across all accounts and sessions on an Authorization Server.
Permission set schemas should be cached with long "expiration" times but shorter "stale" times. Stale Lexicons should get updated (aka, attempt resolution refresh), but if resolution fails, the previously existing stale values can be used. The recommended "stale" lifetime is 24 hours, and this is intended as a firm upper bound on cache lifetime. The firm lower bound on cache lifetime is that of access token lifetimes, meaning 15-30 minutes. The recommended "expiration" lifetime for referencing a permission set for a new auth session is 90 days, but this is not a firm bound. Existing auth sessions should not be impacted by the "expiration" of permission sets from any cache.
At the start of a session, if a permission set can not be resolved (and is not already in a local cache), the auth request will fail.
Usage and Implementation Guidelines
The currently defined resources and parameters do not make use of all of the syntax flexibility. Implementations might decide to focus on currently used syntax instead of the general syntax.
Authorization Servers may chose to use a "Lexicon Aggregator" service to offload lexicon resolution of permission sets. Such services would monitor a full-network firehose to detect updated permission sets. They could contain security logic to protect against NSID domain hijacking or adversarial changes to permission set scope.
Possible Future Changes
The set of resources is expected to expand over time as new functionality is added to the protocol.
Additional attributes are expected to be added to the account and identity resources.
An earlier proposal described a mechanism for purching permission set resolution caches. This would allow synchronized release of updated lexicon resources and updated client software, without concern that permissions would not be available to the client software due to cached resolution. This mechanism has not yet been implemented and is not included in the current specification.