OAuth Client Security in the Atmosphere #3950
devinivy
announced in
Dev Announcements
Replies: 1 comment 1 reply
-
|
Thank you for the informative write up! Is the client assertion backend proposal also applicable to native applications? |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Setting
When a user logs into an app, that app—or in OAuth-speak “client”—is granted some credentials permitting it to act on behalf of the user. These credentials may give the client access to the user’s email, ability to create posts/follows/other records, access private content, or perform sensitive actions on the user’s identity such as updating their handle. It’s an important point that the user is not being granted these credentials—it’s the client who receives them.
The authorization flow takes several steps:
So we can recognize several places in which there could be a mixup. For example:
It’s critical that the user and authorization server agree on which client we intend to grant the credentials to, and that it is indeed that client who receives them. If the user believed they were giving access to a particular client, but access was actually granted to a different party—that would be a major issue impacting security, privacy, and user trust. So in the ATProto OAuth profile there is a mechanism for “client authentication,” allowing the client to show they are who they claim to be.
Security mechanisms in play
There are many ways an attacker may attempt to influence OAuth’s multi-party, multi-step authorization flow. For example, one of the most common real-world OAuth vulnerabilities stems from the client application having an endpoint that supports open redirects (e.g. https://go.bsky.app/redirect?u=) that can be used to forward the authorization code to an attacker.
We also have to keep in mind that authorization servers and clients don’t know about each other ahead of time, and are both heterogenous in their implementations and security practices due to the distributed nature of the protocol. So defense in depth comes into play to protect against what are inevitably imperfect implementations.
Here’s a quick overview of the security mechanisms in play and what they protect against. Further discussion can be found in Best Current Practice for OAuth 2.0 Security, OAuth 2.0 for Browser-Based Applications, and The OAuth 2.1 Authorization Framework.
Registered Redirect URIs
Registered redirect URIs allow the client to place limitations on where the authorization server is allowed to forward the authorization code via redirect; the permitted redirects should always land back on the client. In the ATProto OAuth profile, the permitted redirect URIs for a client are resolved by making an HTTPS request based on the client’s id, which is a URL.
Redirect URI registration is described in OAuth 2.1.
The
stateparameterThe
stateparameter allows the client to check that they indeed initiated the authorization flow prior to trading the authorization code for credentials, i.e. protects from common classes of cross-site request forgery (CSRF) attacks.The
stateparameter is described in OAuth 2.1.Proof Key for Code Exchange (PKCE)
PKCE allows the authorization server to check that the party initiating the authorization flow is the same party that is attempting to trade an authorization code for credentials. This is also a form of CSRF protection.
PKCE is described in RFC 7636 and required under OAuth 2.1.
Demonstrating Proof of Possession (DPoP)
DPoP allows the authorization server to check that the end client-device that initiates the authorization flow is the same one that trades the authorization code for credentials, known as “authorization code binding”. This has significant overlap with PKCE (so long as there’s a separate DPoP key per session), so the greater value add of DPoP is really binding the resulting credentials to the end client-device.
DPoP is described in RFC 9449.
Client Authentication
Client authentication is optional; when it is used we call this a “confidential client”, otherwise a “public client.” In the ATProto OAuth profile client authentication serves two purposes. First, it allows the authorization server to ensure that the client is who they claim to be when issuing credentials to them (during initiation of the authorization flow and token refresh). Second, it allows the client to effectively expire existing sessions en masse, i.e. in case of a breach; or to refuse to continue a specific session.
In traditional OAuth contexts a client is exclusive to a fixed list of authorization servers. So in case of a client breach those authorization server can mitigate the attack by each revoking existing sessions. In ATProto there is no direct relationship between clients and authorization servers, so there is no way to coordinate session revocation in such a scenario. This makes confidential clients’ ability to unilaterally revoke sessions particularly important in the context of ATProto.
ATProto’s flavor of client authentication is described in RFC 7523.
Attacks on Public Clients and Mitigations
Public clients are those that a. cannot prove their identity to the authorization server when they are granted credentials to act on behalf of a user, and b. cannot revoke existing sessions en masse or refuse to extend a given session.
Browser-based clients in which credentials are held by the browser are generally public clients, as these applications are not able to ship the same private key material to all end devices securely. However, backend-for-frontend (BFF) clients in which the credentials are held by the server may also be public if they simply opt not to perform client authentication—though technically BFF clients are required to be confidential.
Note
It is worth noting that clients which hold tokens on the server side generally do make “offline access” possible, which comes with its own risks. Browser-based clients store credentials on the device, so the app must be “awake” for the credentials to be used. This is particularly true of browser-based clients when DPoP is properly employed, since access tokens are bound to the end device/session, and exfiltrating them for later use becomes ineffective. However, DPoP also may not be properly employed by certain clients, e.g. if they fail to securely store private key material.
Client impersonation when receiving credentials
PKCE is a relatively strong enforcement that the same party that initiates the authorization flow is the one that is ultimately issued the credentials. However, it does not enforce that the party who receives the credentials is indeed the client. The client has some control over this by publicly registering redirect URIs that may receive the authorization code, but this is a fairly weak mechanism given a. man-in-the-middle attacks, b. open redirect functionality on the client, c. loose or otherwise improper redirect URI checks by the authorization server, d. misconfiguration or improper control of the client metadata (e.g. by a sysadmin’s ability to shadow the real client metadata).
This is mitigated by client authentication, allowing the client to show that they hold one of a set of private keys whose corresponding public keys are advertised in the client metadata. During token refreshes, the client shows they still hold the key that originated the token.
Arbitrary code execution
Arbitrary code may be executed on the client as the result of a variety of attack vectors, such as cross-site scripting (XSS), remote code execution (RCE), or supply chain attacks. The browser can be a particularly challenging environment.
This means that an untrusted party is able to make use of the user’s credentials as long as a. they go undetected, and b. the sessions are not revoked. The mitigation should be to revoke impacted sessions as soon as they are detected. This can only be done unilaterally by the client through client authentication, i.e. by revoking the public keys previous used for client authentication.
Domain hijacking or other attacks on DNS
The client id-to-metadata mapping and redirect URI provenance rely directly on the security properties of the domain name system (DNS) (also TLS). For example, imagine a client’s domain expires and someone is able to take control of the domain. Or an administrator takes control of user sessions by manipulating the client’s DNS entries directly. Or even DNS spoofing on local networks. In these cases the client id may not have changed, but administrative control over the client has changed wholesale via DNS.
The mitigation should be for all existing sessions to be automatically revoked. Client authentication serves this case, given that the new administrators do not have access to the client’s private key material so they will not be able to authenticate to be issued new refresh tokens. Since access token lifetimes are short, the effect is akin to revocation en masse.
Options for switching from public to confidential
Client is a BFF
If the client is a backend-for-frontend (BFF) then it’s quite straightforward to become a confidential client because there is already a server in play on which to store the key material. In fact, for this reason technically BFFs are required to be confidential.
jwksorjwks_uriin the client metadata document.Client is browser-based
An iterative way for a browser-based client to become confidential is to adopt a variant of the token-mediating backend (TMB) pattern. In the TMB pattern, a frontend and a corresponding backend service maintain their own session together, e.g. using cookies. The frontend calls through the TMB to authenticate the client when initiating the authorization flow, and is eventually redirected back in-browser. Once it comes time to exchange the authorization code for credentials, the client makes a request through the backend service, which adds a client assertion for client authentication, and then forwards the request on to the authorization server’s token endpoint. The backend then receives the credentials which are passed back down to the client.
One tangle with this approach is that the DPoP proofs must be constructed client-side (and DPoP key material live client-side), yet the TMB server must send a proof to the authorization server. That means that the client must know how to generate DPoP proofs for the token endpoint, and the client and TMB need to negotiate DPoP nonces together.
⭐️ A new proposal
There is another proposal we’d like to investigate to make it even more straightforward for browser-based clients to become confidential, with very minimal changes to the ATProto OAuth profile. See Client assertion backend for browser-based applications.
FAQ
Do confidential clients always store credentials on the server?
No—for example, a variant of the token-mediating backend pattern may allow a client to store credentials on the end device while private key material used for client authentication is kept on the server. Another approach is laid out in our proposal for client assertion backend.
Do public clients always store credentials on the device?
No—for example, a BFF that opts not to participate in client authentication would store credentials on the server but would still be a public client. However, it would be simple for a BFF to upgrade to a confidential client and is strongly recommended.
Can we mitigate offline access of BFF?
This is (surprisingly) off the beaten path. We can give this a closer look though. “Offline access” stems from a backend’s persistent access to refresh tokens that may be used when the user is not actively online. And a BFF is characterized primarily by being able to employ access tokens from the server. So perhaps there is a world where the DPoP key material is held on the server, access tokens are held on the server, but refresh tokens are held by the client. A natural followup is whether it’s possible to do this without the server ever observing the refresh token— it’s unclear whether there’s a reasonable way to achieve this. The token-mediating backend also observes the refresh token, but it’s mitigated by the DPoP proofs being generated on the end device. Alternative approaches such as auditability of access token usage on the resource server may be another way to mitigate the risk of offline access.
Beta Was this translation helpful? Give feedback.
All reactions