Reputation: 3583
Given a situation that a public Javascript client wants to access an API endpoint using the Resource Owner Password Credentials Grant type.
A typical request would look like this:
username = "[email protected]"
password = "MyP@assw0rd!"
grant_type = "password"
scope = "openid offline_access"
No client_id
is being passed because the client is incapable of storing the client_secret
and according to the Resource Owner Password Credentials Grant specification it may be omitted.
The authorization server MUST:
- require client authentication for confidential clients or for any client that was issued client credentials (or with other authentication requirements)
The problem is that scope = "openid offline_access"
parameter cannot be validated if client is unknown. In my understanding scopes are designed for describing application permissions.
A question arises straight away.
client_id
is omitted (intentionally and by spec)?Some reasoning:
The reason for omitting client_id
is because anyone could easily retrieve it from the JS client and exploit it.
CORS validation in this case adds nearly zero value, because host header can be manually crafted.
Upvotes: 2
Views: 1219
Reputation: 53958
The suggestion that scopes can be validated per-client in such a flow is incorrect. Since the client_id
is a non-secret identifier, it adds no value. Anyone could grab it from your Javascript implementation and impersonate the Client, phishing for user credentials. Hence the requested scopes may only be handled in a generic, non-client specific way, but moreover:
the OAuth 2.0 spec advises against using the Resource Owner Password Credentials flow in such situations, https://www.rfc-editor.org/rfc/rfc6749#section-1.3.3:
The credentials should only be used when there is a high degree of trust between the resource owner and the client (e.g., the client is part of the device operating system or a highly privileged
application), and when other authorization grant types are not
available (such as an authorization code).
In the in-browser Javascript case there cannot be a high degree of trust between the Resource Owner and the Client since the Client cannot be reliably identified.
Upvotes: 0
Reputation: 57698
TL;DR Jump to the Conclusions section down below...
It's true that the client_id
stored on Javascript can be easily retrieved, but this identifier is not a secret, the specification mentions that explicitly.
The client identifier is not a secret; it is exposed to the resource owner and MUST NOT be used alone for client authentication.
(source: RFC 6749, section 2.2)
This means that it's acceptable to have the client_id
available on your public client so now lets focus on the second issue... client authentication.
The section you quoted says that client authentication must be required for confidential clients or any client that was issued credentials. Your application does not fall into either one of these cases so the client authentication requirement is not applicable.
Okay, so your client does not require (and can't actually perform) client authentication. This causes an issue for your scenario because you want to validate the requested scopes, so lets try to find a compliant solution...
First thing is to find a way to pass the client_id
to the server, because that will be necessary. If the client was confidential this would be passed in the Authorization
header along with its secret, but we're not on that scenario. However, the specification allows the client_secret
to be omitted so lets still use that HTTP header to pass the client identifier.
client_secret: REQUIRED. The client secret. The client MAY omit the parameter if the client secret is an empty string.
(source: RFC 6749, section 2.3.1)
Now, we have the client_id
on the server side, but we can't trust it, not according to the specification, because as we've already mentioned we can't use this identifier alone for client authentication and even if we tried some clever (aka very easy to get it wrong without knowing it) authentication mechanism, there's also:
The authorization server MAY establish a client authentication method with public clients. However, the authorization server MUST NOT rely on public client authentication for the purpose of identifying the client.
(source: RFC 6749, section 2.3)
Damn, this is getting no where! I can't authenticate so I can't trust client identity so I can't validate the scopes.
I hear you, but there's still some hope. Lets focus now on another section of the specification.
When client authentication is not possible, the authorization server SHOULD employ other means to validate the client's identity -- for example, by requiring the registration of the client redirection URI or enlisting the resource owner to confirm identity.
(source: RFC 6749, section 10.1)
JACKPOT! You did enlist the resource owner in the client identity confirmation process, hell he gave his username and password to the client so that settles it.
You do seem to have a way to accomplish the task at hand and still claim specification compliance (at least from the OAuth 2.0 side of things). The only thing left to do is that this is just my opinion, so I wanted to also give you an example with an actual implementation of a resource owner endpoint.
If you go to Auth0 Authentication API - Resource Owner Endpoint you'll find a concrete implementation. It's always good to see how someone else approach the issue. I can already note that this implementation chose to allow client_id
on the request body itself and have a few other custom parameters as options, but this is fine as OAuth 2.0 does not define a strict protocol and just defines the foundations.
One last thing, I wanted to call your attention for is that the offline_access
scope is used in OpenID Connect to signal that you want a refresh token, but you should not do that for public clients. Storing the refresh token, which is generally a long-lived credential, is almost as bad as storing the actual password. See refresh tokens, for some general guidance on these type of tokens.
Upvotes: 1