OAuth 2.0 Authentication Vulnerabilities
portswigger.net
portswigger.net
If you want a list of things that can go wrong, look here: https://tools.ietf.org/id/draft-ietf-oauth-security-topics-1...
Generally you probably do not need OAuth2: https://www.ory.sh/hydra/docs/concepts/before-oauth2/
But if you do don’t roll your own but use proven open source like https://github.com/ory/hydra
It would authenticate who you are, but you have authorize the people yourself later.
> OAuth authentication
> Although not originally intended for this purpose, OAuth has evolved into a means of authenticating users as well.
I'd contest the claim that OAuth was not intended for authentication, because there are no authz uses for the password grant.
There are obvious authz uses for the password grant: you use it when you want to delegate access to a client running on your desktop, which is in your custody, and there's no point in running a multi-legged authorization protocol because you can just log the client in yourself. Your first thought about that might be "that's authentication", but it's not: you don't have to give all-or-nothing access (in theory) to such a client.
100% agree that you should not roll your own. There are lots and lots of options out there with different strengths and weaknesses. Determine what you need and then find the right solution (which may be hydra or something else).
You have extensions like Facebook Connect or OpenID Connect which add on the additional technology and client steps to allow it to be used securely for authentication.
The title is wrong because those involved in the standardization of OAuth 2.0 have yelled from the very beginning not to use it for authentication, but instead use something that builds authentication on top of it.
- https://oauth.net/articles/authentication/
- https://tools.ietf.org/html/rfc6749 - The OAuth 2.0 Authorization Framework
google and TDAmeritrade authenticate their 1st party services with oAuth and it logically makes sense to me.
Agree below though on preconsent.
A general rule of thumb w/r/t/ OAuth is that it starts to make sense when you are delegating authorization to other companies that share your users. Think "TweetLater".
Just so I follow, did you mean underrated…?
so your general rule of thumb is pretty stupid.
0: https://aspsecuritykit.net/guides/implementing-hmac-scheme-t...
OAuth provides an abstraction between the authentication, permission grant and user consent and getting a token representing authorization, and provides additional best practices for things like securing web and native app access, token revocation, token introspection, etc. You probably want to be in the business of improving your SaaS, not designing a secure access system.
That isn't to say you can't build something now with the idea that you will migrate away from that once you hit an inflection point in complexity due to new features.
I'd generally caution against using OAuth until you know you need it.
0: https://aspsecuritykit.net/guides/designing-activity-based-d...
Comments:
* this is a great read about security and OAuth: https://tools.ietf.org/html/draft-ietf-oauth-security-topics... written by experienced folks.
* don't use the implicit grant, please. Use the authorization code grant with PKCE. Here's an example (from a doc I helped write, talking about how bad the implicit grant is: https://fusionauth.io/learn/expert-advice/oauth/modern-guide...
* State can be used for CSRF protection but I've also seen it used to convey, well, state that needs to be carried over. That's legitimate, but make sure you append a random value as suggested.
* The well known endpoints will only be present if the OAuth server supports RFC 8414: https://tools.ietf.org/html/rfc8414 but you can always check the documentation. Most OAuth servers have plenty of public documentation to make their usage easier.
* Re "Flawed redirect_uri validation", the OAuth 2.1 spec has tightened this up some and wants exact URI matching. We have some customers who want wildcard matching, but the redirect_uri check is a fundamental part of the OAuth security architecture, so we've resisted that request.
* "However, by stealing an OAuth code or token, the attacker can gain access to the user's account in their own browser." is a good reminder that you should keep your token lifetimes short. We recommend minutes; use a refresh token to renew the token.
All in all, thought provoking article. There's a reason why more and more teams are outsourcing auth; there's a lot of flexibility in the specs and it's easy to make mistakes. Even when you do it right the first time, there's ongoing maintenance. You should run a bug bounty program and/or regularly pentest your system.
This is a bit of a strange security model. The implicit grant vulnerability mentioned is essentially a supply chain attack where an untrusted library is loaded. True, it can compromise the implicit grant token, but, in a mode where the implicit grant token is hidden, it's still talking as the website, and can do ~most~ anything, more or less that the token can do by making a request with the cookie, or whatever.
PKCE is just a weaker version of that protection.
It's more of a discussion for the standards groups I suppose.
Someone doesn't need to exfiltrate a token to make the client do their malicious requests right on the user's device. And certain external mitigations, like anomalous API usage detection, won't see odd time-of-travel restrictions based on the estimated geolocations of two different IP addresses or a change in the user agent behavior (different user-agent header, different networking stack behavior, etc).
The difference is that some people argue that one of them is in scope for security measures, while the other is out of scope.
Great question. I should have been clearer.
If you use the implicit grant, you can't use the refresh token (there are work arounds like the silent refresh, though). So it's not an applicable question.
With the application code grant, you have other options. Refresh tokens, like all tokens, should be treated with care and not exposed to javascript.
As a sibling comment noted, you can do that by storing it server side. Sure, someone could steal your session and try to access protected resources through the server side code, but that's harder to do than stealing an access token and being able to present a bearer token to any protected resource. (Or a refresh token, which can then be presented to an IdP and exchanged for an access token.)
One other option we recommend is sending down the access and refresh tokens as secure, httponly cookies. In that case, you are relying on the browser's cookie security to protect against XSS attacks. This, while not perfect, is pretty darn good, and if there's a widespread XSS cookie vulnerability:
1. Everyone with a browser client will probably have issues.
2. You could invalidate all the refresh tokens at an IdP. Pain in the but for users, but this is similar to what GitHub did earlier this month.
Now that we are supposed to just use PKCE, we can stop using state for a nonce and CSRF protection and just use it for state.
> * "However, by stealing an OAuth code or token, the attacker can gain access to the user's account in their own browser." is a good reminder that you should keep your token lifetimes short. We recommend minutes; use a refresh token to renew the token.
Stealing codes does not work because PKCE, you need to steal at least the entire request and response, will be blocked if PKCE is using a cryptographic transform.
Protections from stealing tokens are confidential clients (requires a remote machine compromise) or proof of possession like MTLS or DPOP (requires private key exfiltration).
Generally the most paranoid access token lifetime recommended is ~10 minutes. There are token revocation methods that let you go longer, but revocation is an active process while token refreshes can just fail because of some change of application state.
* PKCE is required for all OAuth clients using the authorization code flow
* Redirect URIs must be compared using exact string matching
* The Implicit grant (response_type=token) is omitted from this specification
* The Resource Owner Password Credentials grant is omitted from this specification
* Bearer token usage omits the use of bearer tokens in the query string of URIs
* Refresh tokens for public clients must either be sender-constrained or one-time use
> the server does not have any secrets or passwords to compare with the submitted data, which means that it is implicitly trusted
The access token should be signed right? So it's not implicitly trusted. The server must of course validate the access token using the public key indicated in the token, and of course only accept tokens from trusted providers. Maybe I'm not reading this right.
As far as validating tokens, access tokens are 'opaque' in OAuth2; the token format is not defined by the spec. (In OIDC, they are JSON web tokens.) For OAuth, you can make a call to the introspect endpoint and the Authorization Server will tell you if it is a valid token.
In practice, OAuth servers often issue JWTs, and there's a draft spec out to make them interoperable: https://tools.ietf.org/html/draft-ietf-oauth-access-token-jw...
If you are a resource server consuming an access token and you know the token is a JWT, you should check the following at a minimum:
* the token is signed according to the algo and the key that you expect
* the token is not expired (the `exp` claim) and is currently valid (the `nbf` claim, if present)
* the token is issued by who you expect (the `iss` claim) (which is the point you are making about 'trusted providers')
* the token is issued for you (the `aud` claim)
Most libraries I have interacted with check the first two, but not the last two.Important point! Especially with JWT. It must be the algorithm you EXPECT, not the algorithm in the token. Someone flubbed up and made the "none" algorithm mandatory for spec-compliant implementations. Unfortunately, that can result in any JWT being treated as valid.
It's a chicken-and-egg problem that the designers missed: you can't trust the token until you've validated it, but you'd have to believe the value of the algo field before you've validated it in order to check the signature.
There are libraries that will see "none" in the algo field and treat that as "this token doesn't have a signature that needs to be validated, so it's good".
Which spec are you referring to? 7519? Per section 6 ( https://tools.ietf.org/html/rfc7519#section-6 ) "none" is an optional feature of JWT.
I am not aware of any spec related to OAuth which requires a server to issue or accept a JWT with an algo of "none". Can you point one out to me?
From the AS side, our product won't even allow you to "sign" a JWT with a value of none. I'm not sure about other authorization servers.
And I always recommend that if a resource server ever sees an algorithm of "none" it should throw out the JWT full stop.
> It's a chicken-and-egg problem that the designers missed: you can't trust the token until you've validated it, but you'd have to believe the value of the algo field before you've validated it in order to check the signature.
What is the way around this? Looks like Paseto tokens (an alternative I've heard mentioned) works by not allowing unsigned tokens: https://github.com/paragonie/paseto
Is there another way to fix this?
> There are libraries that will see "none" in the algo field and treat that as "this token doesn't have a signature that needs to be validated, so it's good".
Spooky! Can you share these so I can stay far away from them? :)
OK, this one's on me. I can't find the reference, and I took an article at face value: https://auth0.com/blog/critical-vulnerabilities-in-json-web-...
There are libraries that allow Bad Things, though, and best practice now is just to ignore the `alg` field. As the github repo you linked says, "There have been ways to exploit JWT libraries by replacing RS256 with HS256 and using the known public key as the HMAC-SHA256 key, thereby allowing arbitrary token forgery."
My goodness I wish I was allowed to disclose a place where this was used for privilege escalation and not checked in any way.
Encrypt your access tokens by default if you want to guarantee clever clients don't try to take a dependency on them.
OAuth doesn't define the concept of an identity token, hence it not being usable itself for authentication OpenID Connect extends OAuth with an identity token, which is of course readable by the client.
To be fair, Facebook Connect explicitly made this choice to combine the identity token into the access token - so their access token is necessarily client-readable and verifiable.
Later they use this language the clarify the situation. I suppose the situation being described is when the token has no information that can track to a specific user id. I guess devs are just checking that a token decrypts and aren't verifying it matches the user id in the request?
Seems like basic security 101.