Proposal: OAuth Scopes #3655
Replies: 8 comments 7 replies
-
|
Agree that you don't really want a wildcard because you may end up with highly fragmented namespaces for security and not semantic purposes. re: wildcards and bundling - has there been consideration of versioning the auth-bundles and therefore allowing an app to request new permissions by identifying that the version has moved up? I'm concerned about the case where a developer needs to handle both 'which individual permissions are missing' and 'which bundles have been granted', which may lead to over/under-privileged tokens (which can be an issue if they're long-lived) especially where you are interacting with someone else's app via an API. My other comment is around some kind of generic structure - I think there's a need for non-data attributes. Actions such as 'initiate account recovery', or 'delete my account' are an example of a permission that you may want to grant but doesn't really have a data equivalent. Having a spec for these transactional scopes would avoid some not-great workarounds in the future (i.e. 'you have perms on this collection, therefore you can take this action...'). |
Beta Was this translation helpful? Give feedback.
-
|
This is cool. So Streamplace needs like...
Maybe a few more. Perhaps |
Beta Was this translation helpful? Give feedback.
-
|
How should an app like PDSls are expected to work going forward where the expectancy is that it gets permission to do writes on any and all records, even on unknown ones? |
Beta Was this translation helpful? Give feedback.
-
|
This is a great start to a complex topic, but I worry that we are just reinventing https://github.com/cedar-policy/cedar with the Lexicon system. Have you considered integrating with some existing authz framework/library vs doing everything custom? |
Beta Was this translation helpful? Give feedback.
-
|
In the lexicon definition you have the type as You're right about this granularity being difficult for both users and developers to parse, and I have concerns about the size of the access token that is going to carry them. Remember the jwt needs to be sent with every request, in a header. While there's no RFC standard for this different web servers put their own limits on header size, for example, Apache is 8k, as is NodeJS, IIS is 16k. Additionally, some also place limits on the total size of all the headers. Descriptions of bundles and permissions/auth-resources need to cope with internationalization :) Providing a standard default description in multiple languages that any PDS implementation can use would help present users with the same experience in authorization regardless of service. |
Beta Was this translation helpful? Give feedback.
-
|
I like what is here. It feels very elegant to reuse the tools (lexicons) devs will already have to learn. I'm a little worried about overloading the I'd still be happy with description, but the docs would have to make clear that this description is very different from the rest in a way that I feel would be annoying to write. |
Beta Was this translation helpful? Give feedback.
-
|
@dholms ! great meeting you last weekend! I haven't fully wrapped my head around your
|
Beta Was this translation helpful? Give feedback.
-
|
Like the auth-bundle and auth-resource approach as a common starting point. Also think we should double click on the aud path for detailing the service authentication. From an Agoris (caring communities) PoV we will have regional cooperations caring for around 15-30k citizens. Some will want to use their own node / hosting / pds / theming and others will want to use a more centralized and regional full service approach. In any case we will have private, commercial and a mix of both user groups that each offer and consume services. And it is up to the service provider to spec as to whom they want to offer their services both from a domain, user group and regional perspective. I think we can design almost all requirement with the Lexicon based approach. Having worked with enterprise scale IAMs and Keycloak a lot we also might want to discuss the generic use of „allow“ vs. „deny“ in the schemas in order to be more flexible in designing specific policies. Also echoing other voices we should think about whether we can learn from the current IPSIE efforts to see what we can re-use instead of reinventing. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Note: a more complete proposal was published in July 2025: https://github.com/bluesky-social/proposals/tree/main/0011-auth-scopes
We’re continuing work on rolling OAuth out to the atproto network. Just recently, the Bluesky PDS distribution gained the ability for any network application to create an account on it (#3621). This week, we’re also updating the Bluesky’s PDS deployment (bsky.social) to allow for the same. This will allow any app in the network to allow users to create accounts “from that app” (through an OAuth flow) rather than directing the user to create an account through the Bluesky application.
A core missing piece of the OAuth stack is the ability for applications to request more tightly scoped authorization from the user’s PDS. Currently the scope level is roughly analogous to what a user receives from using an “app password”. Ideally a client will be able to request access to only the record types and methods that it needs for its application experience.
This document isn’t quite at the “RFC stage” yet (though we of course encourage comments on it!). We’re hoping to get to a more complete proposal within the next few weeks. In the meantime, this document is primarily meant to circulate the current ideas that we’ve been chewing on. We especially wanted to get it out ahead of AtmosphereConf as we know this topic is top of mind for many in the developer community. We encourage feedback, questions, and thoughts!
PDS Resources
OAuth scopes relate to resources that a PDS owns.
These are, roughly, and at a high level, the resources that the PDS owns and for which we want to allow access to network applications.
To demonstrate, the needs for a client similar to the Bluesky app:
app.bsky.feed.post(create, delete)app.bsky.feed.like(create, delete)did:web:bsky.app#bsky_appview<any>did:web:api.bsky.chat#bsky_chat<any><any>app.bsky.feed.getFeedSkeleton(& friends)Encoding these resources as conventional OAuth strings may look something like the following:
Evolvable resource semantics
The problem with the strings listed above is that they’re a little magical. They would probably need to be defined in the OAuth spec or in the Lexicon language (if we start referencing them in Lexicons which we’ll get to later). In a multi-party, low-coordination network like atproto, we also need a way to evolve them over time. Fortunately we have a system for making application semantics both legible and evolvable: Lexicon!
The idea is that we can push these descriptions of resources into the schema layer. A schema that represents the repository resource could look like the example below. Note that this syntax is not nailed down and is only meant to give intuition.
{ "lexicon": 1, "id": "com.atproto.permissions.repo", "defs": { "main": { "type": "auth-resource", "description": "Grants access to records in the public repo", "parameters": { "type": "params", "required": ["collection"], "properties": { "collection": { "type": "string", "description": "The collection being granted access to" }, "create": { "type": "boolean", "default": false }, "udpate": { "type": "boolean", "default": false }, "delete": { "type": "boolean", "default": false } } } } } }Right now, we would only expect
auth-resourceschemas to be defined in thecom.atprotonamespace. These are not meant to enable dynamic runtime coordination. Rather they would be high-context and require the implementation of the PDS to change so that it could understand the semantics in the schema. Theauth-resourceschema is only meant as a coordination mechanism for evolving these semantics over time or introducing new resources to the PDS.These
auth-resourcescould then be encoded as OAuth scope strings as such:Coordinating scope bundles
For an app like the Bluesky app to function, it would need to request a lot of these scopes. This presents a UX issue (long lists of scopes are pretty ugly), but also a security UX issue. Users that are routinely presented with long lists of scopes will become less discerning as to which scopes they approve and it becomes easier to sneak unwanted scopes in.
Therefore, we want the authorization server to be able to bundle scopes and give them human-readable descriptions.
We’re faced with a unique problem in the atproto network because the PDS (the authorization & resource server) is generic, but needs to provide human-readable descriptions of scopes for application-specific records and methods - resources that it doesn’t necessarily understand!
Atproto is a multi-party network: clients, PDSs, and application developers all need to coordinate in an n-to-n-to-n manner.
The developer of a given application (in the sense of a “network application”, or a set of Lexicons describing an atproto app) is in an ideal position to both bundle and describe these scopes.
Just as we coordinate record semantics using Lexicon, we can also coordinate authorization semantics using Lexicon.
The idea is to introduce a new lexicon schema type
auth-bundle(name pending) that defines these bundles of scopes. Again note that this is not a final version of the schema and is only meant to give intuition.{ "lexicon": 1, "id": "app.bsky.auth.base", "defs": { "main": { "type": "auth-bundle", // This description is shown to the user // Note: will also need to add a mechanism for internationalization "description": "Creation of bluesky posts & likes and authenticate the user towards Bluesky AppViews.", "permissions": [ { "ref": "com.atproto.permissions.repo", "params": { "collection": "app.bsky.feed.post", "create": true, "update": true, "delete": true } }, { "ref": "com.atproto.permissions.repo", "params": { "collection": "app.bsky.feed.like", "crate": true, "update": true, "delete": true } }, { "ref": "com.atproto.permissions.repo", "params": { "collection": "app.bsky.feed.like", "create": true, "delete": true } }, { "ref": "com.atproto.permissions.serviceAuth", "params": { // Need some mechanism for parameterizing this with an `aud`. More on this below "aud": "...", // Perhaps add in a wildcard mechanism for *any* method "lxm": [ "app.bsky.actor.getPreferences", "app.bsky.actor.putPreferences", "app.bsky.actor.getProfile", "app.bsky.actor.putProfiles", "app.bsky.feed.getActorLikes", "app.bsky.feed.getAuthorFeed", "app.bsky.feed.getFeed" // ... ] } }, { "ref": "com.atproto.permissions.serviceAuth", "params": { "lxm": "app.bsky.feed.getFeedSkeleton" } } ] } } }Restricting application authority
Because the goal of the
auth-bundleis to hide a large set of permissions under a single human-readable name, we cannot give applications the unmediated ability to bundle any resource into their authorization scopes. Therefore, we need to restrict which resources are able to be included in anauth-bundledocument.Roughly this is “only resources (records & methods) related to the NSID of the
auth-bundledocument”.The first mechanism that we’re looking at is basing this on the syntax of the
auth-bundleNSID. Generally, we try to refrain from relying on hierarchy in the NSID structure (see the lexicon resolution spec for more details). However in this case, the semantic would be “Anauth-bundleschema can contain resources for any sibling or children NSIDs” with the possible added semantic that.authis reserved as a special keyword that is essentially ignored when doing this syntactic check.So for instance:
app.example.baseAuthcan referenceapp.example.recordandapp.example.methodapp.bsky.auth.basecan reference[app.bsky.feed.post](http://app.bsky.feed.post/)andapp.bsky.graph.muteActorThe other mechanism that we’re looking into is basing this on the Lexicon documents authority, that is the repository that the document is contained in. So
app.bsky.auth.basewould be able to referenceapp.bsky.feed.postby nature of it being in the same repository. This is nice because it avoids special keywords and hierarchical semantics, but is somewhat unsatisfying because it mixes hosting of lexicons in with the auth scope semantics.Service Authentication
Service auth is a weird one because it can be parameterized by the client with the audience that the auth is for. Unlike all of the other permissions that can be described by the Lexicon developer, the audience for service auth is a client choice.
Especially with sensitive services (like a DMs service for instance), it is important that a user is aware which service they are giving authentication access to the client to.
One option is to parameterize the
auth-bundledocument so that clients can request a scope likeapp.bsky.auth.base?appviewAud=did:web:bsky.app. In this caseappviewAudwould be a parameter that is defined in theapp.bsky.auth.basedocument.Another option is to rely on a particular syntax that parameterizes all audiences for any service auth in a given document. This could look something like
app.bsky.auth.base@did:web:bsky.app. The downside of this syntax is that any scopes with different audiences (for instance any audience forapp.bsky.feed.getFeedSkeleton) would need to be requested as a different scope.The final option is to not allow service auth to be specified in the
auth-bundleschema, meaning that clients must also request the decomposedauth-resourcestrings (in which they will supply the audience as a parameter) when requesting service auth.In any of these options, the audience would be shown to the user as the hostname that the requested DID resolves to.
Clients using both auth-bundles & auth-resources
Clients can use both
auth-bundles andauth-resources to obtain the authorization that they need to operate. This way, if an application designer does a bad job bundling scopes, a client can request exactly the authorization they need. Clients can also request scopes from across application spaces.For instance, a client may request the following scopes:
Reliance on Lexicon Resolution
This entire proposal relies on Lexicon resolution and also significantly increases the security concerns of that resolution. You can read more about the lexicon resolution spec Here.
If Lexicon resolution fails, the client will receive an error. Should this occur, the client could fallback to requesting all the individual permissions. This would provide a less ideal UX but it provides a decent degradation.
It will be important that PDSs (fairly) rapidly refresh lexicon documents. This will allow updates in authorization semantics to evolve over time to support new features, and also allow application designers to rapidly patch up any possible security holes in their
auth-bundledocuments. We expect a TTL of 5 minutes or so will suffice for refreshing auth scope schemas.Caching of Lexicon can also be enabled by a Lexicon aggregator when performing lexicon resolution. This introduction of a party that a PDS trusts to perform lexicon resolution may help with both availability concerns and some security concerns (for instance throttling dramatic changes to a bundle of auth scopes).
We also intend to have some way to “bind” access tokens to a specific version of the scope requested. We haven’t quite nailed down the mechanism for this. A likely candidate is just be an integrity check against the CID of the scope document.
Why no wildcards?
One major design decision that we ended up making was to not allow hierarchical wildcards in scopes. For instance
app.bsky.feed.*. Because this was such a big discussion point for us, it seemed like it warranted an explanationThe upsides of wildcards are:
auth-bundledocuments for new features and fewer refreshes of access tokens for those new scopesHowever introducing wildcards also comes with some usability & security issues.
First, wildcards seem most useful if they have exclusions (for instance,
app.bsky.feed.*, but notapp.bsky.feed.generator). At least in Bluesky’s case this would be necessary. This may be a data-modeling error on our part (we probably shouldn’t have put feed generator records in the same namespace as likes 😅), but also if we made that mistake, it is likely that other application developers will as well. Exclusions are inherently unsafe as it is much more likely to forget to exclude a sensitive feature (because “it works on my computer”) than to add a missing feature to a scope. Even if we disallow exclusions, this speaks to a general problem of overloading schema names with authorization semantics which seems prone to footguns.Wildcards also introduce a notion of hierarchy between NSID hostnames (for instance,
app.bsky.*) which does not exist in the lexicon resolution protocol. While we could restrict wildcards to the top-level, this becomes a somewhat confusing semantic.Finally, wildcards don’t buy us any new capability that we do not have from being able to just refresh auth scope documents. It seems better for the ecosystem to just make sure that scope refreshes work better than to try to get around that through dynamism in the syntax of the scopes.
All that being said, wildcards may still pop up specifically in the context of service auth audiences. For instance, being able to call
app.bsky.feed.getFeedSkeletonon any service.Beta Was this translation helpful? Give feedback.
All reactions