Another issue with OAuth is the assumption that the browser part of the flow (where the real user enters their username and password on the issuer website) is actually done by a real user. When I was trying to automate EV charging, I looked at quite a few OAuth implementations and many of them were doing things like storing the user's credentials (including their password) then doing the entire OAuth flow with browser automation. I imagine this was the tip of the iceberg.
These days with a lot of non-standards-compliant OAuth implementations behind me, my conclusion is a lot of it is a giant waste of time, and it often adds completely pointless complexity for many of the cases it's used for. And as shown in the article, all the complexity often adds exploit vectors.
For 90% of API use cases just issue a revokable Bearer Token and be done with it. You are not an app platform!!!
[Qualifier: the EV API's I experimented with were a good use-case for OAuth, as they were user-first, based on mobile apps authenticated with user/password credentials. But I've worked with a lot of third party vendor API's where it was purely server-to-server access, but for some reason wrapped in OAuth... because... architects and compliance something something]
> I've worked with a lot of third party vendor API's where it was purely server-to-server access, but for some reason wrapped in OAuth... because... architects and compliance something something
As someone who's implemented a bunch of these..
It's less about compliance and more about getting everything "speaking OAuth"
Once you've converted your user-facing flows to use OAuth and you're dealing with scopes, tokens, etc, converting the backend to also use it makes a lot of sense. Then you have ONE path for token validation, inspecting/validating scopes, and client id/secret issuance whether you're on the front or back end. Having one model for people to understand makes things tangibly simpler and shrinks the libraries/tools you need.
If you ONLY have backend (service to service) flows with a simple authZ model, OAuth is usually overkill.
Look I'll sing LDAP's praises when it's appropriate but this really isn't it. It's less of SSO and more "Same Password Everywhere" which are not at all the same experience from the user perspective. It also teaches your users to plug their creds into random login screens instead of the one-true-login-page they can verify is us.
Like as hackers we make fun of the phishing training that teaches you to avoid sketchy links in emails while simultaneously the real emails have the sketchiest links of all time. Using LDAP for this use-case is our version of that.
We should teach users to put their passwords into very few, limited, and trusted places. The cool new social app is NOT trusted. That random app on your phone is NOT trusted.
That xkcd is about the downside of using the dame password on many services. No service is really trusted so what we should teach people to do is, as usual, to use a password manager to randomly generate a new password for every service. Maybe that's what you were advocating but it was not clear to me.
We should also teach to ourselves to throw away all those weird rules about passwords, like 6 to 15 characters max, letters and digits and a random set of punctuation characters liked by the site owner. Why those limits when they are storing a hash anyway?
Well lets talk about your average 'bigcorp'. You've got laptops you need to log into, you've got network shares you need to log into, you've got internal web applications you need to log into, you've got external saas bullshit you need to log into, you have some cloud accounts you need to access... You need to manage this for thousands of people, probably 10s of thousands of people.
You don't want your average user to have a great many usernames and passwords, maybe two or three at most to manage. You also probably don't want to have them share those credentials.. though without 'one big system' you can't really prevent that. You also probably want some kind of system with 2factor support through a few different methods, rolling code, sms, yubikey, etc.
LDAP ain't it.
From where I set Oauth seems to be the current solution with the least number of downsides. It's complicated for sure, the flows are easy to fuck up, but I haven't seen a better solution.
Maybe Passkey is it, i keep meaning to read the rundown but I just haven't found the time... and also it's realativly new enough that support and open source solutions might be a little sparse...
One really shitty thing is that people keep inventing those badly-though protocols like OAuth, and yet nobody (as in no browser) ever implemented LDAP over the web.
Windows has that stupid rule where LDAP runs all over the OS or nowhere at all, and Linux has that idea that LDAP is some add-on you assemble by connecting a jigsaw of pieces. Nothing makes it reasonable to publish a domain on the web, where people can authenticate on many of them, and send the tokens where needed.
(Well, actually Firefox does most of it, and you can use it and assemble the Linux pieces so it works. It just doesn't work in practice.)
The thing is MS LDAP pretty much sucks all the air out of the room in the LDAP world. If you're going to write anything that supports LDAP you pretty much are going to write it to support Windows then most people just suggest to use Windows in the first place
> it was purely server-to-server access, but for some reason wrapped in OAuth... because... architects and compliance something something]
That sometimes feels like the bane of my existence. Everything has to be OAuth and use refresh tokens, certificates etc. when I never care or use any user data. No one logs in. This is all our server talking to their server. Revokable Bearer Tokens used to be used, but every API that updates switched to OAuth which makes everything more complicated.
This is what the client_credentials grant flow is intended for. If you're using any interactive grant flows to secure S2S communications, you're doing it wrong.
The oaith spec requires a callback URL to work and usually that url has to be “registered”/authorised with the server. Most of these apps are using the api “unofficially” and the only way to do that is with browser automation. Otherwise once the login is complete the redirect with the authorised token won’t get to your app - it will go to the official website or app.
They then pluck the authorised token out of the cookie store.
Not really. The libraries should do what my own app does: render the vendor website (Tesla or whoever) in a browser window and let the user enter their creds securely. You can then capture the auth code from the redirect just fine.
There are right and wrong ways to do OAuth both in server and client, and many, many implementations do it wrong.
That works for GUI apps I agree though doesn’t work for server side stuff. Which I suspect the software the OP meant is. But yes better than storing the password if you are on a desktop.
Right, so my app is an electron app, and you can hook into the redirect. It’s still a little bit hacking the flow but not as bad as full browser automation
If you control the client, you can. Because when you get the redirect response, you then take the auth code that is included there, and you don’t follow the redirect.
Then you send the auth code to your backend instead.
> because... architects and compliance something something
At work the tooling we use for our services outright requires OAuth if you want authentication. And yeah, that's a large factor implying we should use something else, but there are many coordination problems for that.
I imagine plenty of people have a similar problem.
Anyway, if your client is saving your password, then I'd say that's not a good use-case for OAuth. At this point I'm not sure there exist a good use-case for OAuth.
What I hate about OAuth is how slow and cumbersome it makes the process across the web. Everything requires login with google for example, but that flow is so slow that it’s what I dread the most of all auth. Especially when I see the page reloading multiple times because it wants so many other things besides just auth.
Tesla only just recently opened the ability to be an official oauth application, so all apps before now either required storing username+password or the long-lived token it sends back once you authenticate.
> the OAuth protocol itself is indeed correctly designed and is secure by nature. However, to use it with a web service requires integrating it into that service’s existing platform, which is where the trouble starts
That's my main beef with OAuth. I like having a standard in the middle, I also like OAuth, and I think I understand some of the flows well (I have enough knowledge to know I'm not an expert).
The main issue is that it is complex enough that you can botch the implementation in multiple places (eg misuse of the refresg_token), flexible enough that you can take the wrong shortcuts (eg use the wrong flow), and most implementations have semi-transparent intermediary points (eg Jwt that you can see into) that make you feel like you understand the whole thing while you only get half of it - or once again use a component for something it should not be used (id_token for authorization being the most common).
It's better than inventing your own scheme, which is certain to fail. But most people don't spend or don't get the time they need to understand properly. It doesn't result in gaps necessarily, but it can.
And was astounded by how many of them came in the class of "OAuth Exploits". And far from a list of specific CVEs in some random app, you seemed to be able to go from one common implementation bug to the next. You walk away wondering how anyone ever gets it right.
You can use your existing API tokens as the OAuth access_token. Refresh tokens are optional. None of it has to be a JWT. Doing this prevents having to support multiple API auth methods.
That's precisely the point. You can do those things, or you can not do them. And there are paths in the combinatorial that leave you with an open door.
Re: OIDC, that's yet another one. The average dev can't tell you the difference between both - or won't even know the term OIDC and call it OAuth.
Securing an OAuth implementation is hard. There’ve been several appendices added to the spec that many developers are probably not aware of [0]. Remember to check for current best practices to learn about the latest mitigations and protocol updates!
There is also an MFA bypass that I see often, it's the Resource Owner Password Credentials (ROPC) flow in OAuth.
Especially when you have an Microsoft M365/Azure tenant. Pretty much every client that I have ever tested had this issue. When ROPC is configured (which is the default) then you can just use a simple password to logon (and it bypasses MFA).
I see this a lot too. It makes password spraying very easy. I don't see anything in the article about remediation. Do you have any info for disabling it?
> Vidio, an online streaming platform with 100 million monthly active users, the researchers found that when logging into the site through Facebook, the site did not verify the token — which the website developers and not OAuth must do. Because of this, an attacker could manipulate the API calls to insert an access token generated for a different application, the researchers found.
How does this work? I thought the token was an opaque hex string and only had meaning when sent back to the original server?
The token can be a opaque string but doesn't have to be. Often, it's a JWT which can be decoded, validated, and used locally without a trip to the auth server.
It looks like Vidio, etc validated the signature of the JWT properly but didn't check the "aud" or audience claim, which defines who should use it. Therefore, it was a valid token, it was for the target user, but the token itself wasn't for Vidio.
It's the equivalent of buying a ticket for a United Airlines flight, going to the airport, and United letting you get on ANY flight because you have a valid ticket.
Background: I helped build the Okta OAuth product (not related to the breaches) and I'm the author of the top OAuth/OIDC course on linkedIn.
Heh, this is exactly how I got on the wrong flight once (the gate changed and I didn’t realize it), the machine didn’t tell the person I was getting on the wrong flight and the attendant didn’t notice.
The only reason I discovered I was on the flight is because someone else had a ticket for my seat. The security people were very curious how I did it, both pilots were very pissed (both flights had to be stopped from leaving the gate until they were sure I didn't leave a bag on one of the flights, yay terrorism). But now you can’t do that any more.
Depends. The core issue is that a lot of developers confuse authentication and authorization. And oauth kind of muddles the water on that front by vaguely doing both but not really. All it does really is exchange tokens (assertions).
Anything JWT related is orthogonal to oauth. Oauth does not actually provide any semantics for the tokens you exchange. They might be JWTs and you might be able to verify them. Or they might just be some random number, a hash, or some kind of database id. But you would have to know in advance what it is exactly and what the proper process for verifying the token is.
In the case of a random number or some kind of session id, the process of verifying that token would involve looking it up from some kind of database or via some kind of API.
Alternatively, with a JWT, it might be a signed one (hopefully) and you'd be able to check the signature if you have the public key. All you have at this point is a blob that was signed by someone that you might or might not trust. The claims and other meta data in the token would tell you hopefully who the person is (authentication) and the level of access they might be given (authorization). The JWT spec defines some fields that you might use for that. But it's up to you to check all those things and verify them.
The point is, that a lot of this stuff is simply implementation/vendor specific and you just need to know what to check and how and why. And it's on you to do all those things.
This is not an issue with oauth, or JWTs, but with incompetent developers doing things wrong or being lazy/negligent/indifferent. Which sadly is very common. Lots of people that get busy implementing all sorts of stuff without any formal training. And a lot of this stuff isn't even taught properly in schools/universities.
This falls in the same category as putting passwords plain text in a database, not setting up ssl certificates, running your database on an open port on the internet, and similar things that developers do wrong all the time. Because they don't even know the basics of how to do things right. A lot of these things would be caught during any half decent audit. And quite a few of those things could expose the companies involved to some expensive law suits. Which is why lots of companies spend money on audits because they don't like expensive law suits.
> incompetent developers doing things wrong or being lazy/negligent/indifferent. Which sadly is very common.
I was code reviewing a dev who was implementing some auth and I mentioned they should log something for audit purposes. They said 'auditing isn't one of the requirements of this ticket' ... at which point, I knew it was time to leave. Like do you need someone to write a ticket to do your job? When people come ask "who logged in and deleted my org's content" ... you better have an answer. This is software engineering 201: logging.
It's like an electrical engineer wiring a house not up to code and saying 'I don't care because the customer didn't ask for it to be up to code.'
I get what you're saying, but...
Logging is not auditing, AFAIK.
Also, and I may be very wrong based on the way that org prepared tasks, he may have been signaling scope creep... If it's not in the task, you don't do it. If you think it should be done, you backlog it...
Not to be pedantic, but it's called an "Audit Log", usually. So, it is logging, though if you don't keep your logs forever, it is usually logged to a db or something (but that isn't necessary -- depends on the industry, privacy policy, etc.).
> If it's not in the task, you don't do it. If you think it should be done, you backlog it...
You shouldn't have to be told to do your job. One day, someone is going to ask why X wasn't done (when it clearly should have been, by all industry standards, and especially when it is exactly one line of code) and "I was just following orders" doesn't work in any court, including the ones that get you fired.
When implementing auth, almost no PM is going to write down "mitigates timing attacks" as part of the acceptance criteria, but you do it anyway because it is the right way to do it.
And none of those things come down to the individual dev in any sizable organization I've ever seen. Suddenly you've started adding a db schema new db schema to the requirements and are likely blowing up the sprint.
You push the ticker back up and fire off a few email to PM and ask what the hell they are doing with the requirements
That's an implementation detail so far from what we're talking about that its almost nonsense. Even if you don't have any infrastructure at all, simply injecting a NullLogger and audit logging to it is better than nothing at all (and filing a ticket to address that ASAP). That's two lines of code, but what I was talking about was exactly one line of code because the infrastructure already existed.
I agree with the comment above yours but also feel that this is... different style of work? Or attitude? Not sure how to name it but feels like I also brushed up against this numerous times.
I assume that line of "what is scope creep and what is assumed to be in scope even if not written down" has to be blurry but I tend to judge people on which side they end up on, because I feel it correlates to indifference etc.
Ultimately, it feels like a bad approach to work relationships - you actually have to do personal groundwork in your team to unblur the line to where you and your team agrees. And if that doesn't work out - leaving is a valid, if sad, option.
> he may have been signaling scope creep... If it's not in the task, you don't do it. If you think it should be done, you backlog it...
Correct. Especially if you’re working for a client that’s billing by hour. Regardless, new work changes scope and blah blah agile. Yes it’s overhead, and it also creates a chain of who did what, and when.
While what you're saying applies to OAuth in general, there are a lot less variables in this situation.
This was specifically "Sign in with.." so we know it was OpenID Connect which mandates signed JWTs for ID tokens. Those JWTs would have an issuer (the social provider) to determine their source and the client id (to determine the intended app) so those aren't in question here.
Further, since these are ID tokens, they WOULD provide authentication information (technically identity info) and minimal authorization info.
The thing about cryptography you always hear being said is "don't roll your own crypto", because you don't know all the complexities and failure modes that experts have researched.
In o-auth, just you comment mentions multiple ways of doing things, and different responsibilities the developers have in different circumstances. Essentially the developer is forced roll some of their own auth.
Compare that, for example, to say how you use TLS. You just use the standard libs bundled everywhere, and open connection, and the the system does all the validation for you. The complexity is there, but average developer is not expected to understand it.
O-auth simply exposes too many details to the average developer, and too many options to use them differently, for it to ever be secure.
That's just the way oauth works. I agree it's a messy protocol. Classic design by committee stuff. Exactly what you get when mutually incompatible security product vendors try to create a standard that covers what all of them do.
Unfortunately, there are no good alternatives that don't have this problem. Basically people use all sorts of commercial products in the hopes that it will act like magic security pixie dust. Most of them are super complicated to work with. Or expensive. Or both.
There's just no way around the fact that you need to know what you are doing and how stuff sticks together.
While what you say is true about OAuth2, this isn't what "don't roll your own crypto" refers to, I think. It's more like "don't start implementing crypto routines yourself".
In my case, I was initially confused by the fact that in most examples (specifically for Azure AD, which is what I've used), they give you an access token for another app, here MS Graph API. It specifically says you're not supposed to read that access token, so you can't validate it. You should use the ID token instead (which can be validated), but in the default configuration, the ID token doesn't carry much information, so you need to call the `userinfo` endpoint with said opaque access token.
For Microsoft it's another case of them going out of their way to exercise every possible inch of lee-way permitted by the specifications. No one in their right mind should expect a userinfo endpoint to only be accessible at an entirely different host. It's doubly stupid that Azure AD provides no other options for remote token validation, conveniently skipping the token introspection extension that _every other IdP supports._
People writing OAuth2 clients reasonably assume that trustworthy IdP's will behave normally and predictably, which means Azure AD/Graph(/Entra ID?) is probably not compatible with the libraries you're using. Fortunately Microsoft provides their own perplexing, over-engineered SDK for a handful of languages. All you have to do is rip apart your existing OAuth2 plumbing and carve another Microsoft-sized hole into your otherwise nice and standardized application.
In my case, for apps that only need to authenticate users with AzureAD, I've added the required information to the tokens and called it a day.
I wouldn't bet the farm on it, but I seem to remember that by default, the mozilla-oidc implementation for Django was expecting an userinfo-like endpoint. And I remember wasting an unbelievable amount of time trying to figure how to add information to that endpoint. I've never found anything, I just patched the lib to retrieve the relevant fields from the tokens.
The articles say these sites were validating the tokens - as in checking signatures - so they were doing that properly.
The issuer is another matter. Facebook or any other social provider is different than using AD. With AD, every customer would have a separate and distinct issuer related to their specific org and config. For social auth, there would be ONE issuer that everyone shares.
The original article [1] explains it much further:
> As you read previously, according to the Facebook documentation, when Vidio.com receives the access token from the user, Vidio should verify that the access token was generated to its App ID (92356) by calling the https://graph.facebook.com/debug_token API.
The access token usually has an `aud` field that says for whom it is.
I'm not familiar with Gitea's implementation, but reading your link, it would seem that it acts as an oauth2 provider so that 3rd parties can access Gitea, not some other random app.
> Gitea supports acting as an OAuth2 provider to allow third party applications to access its resources with the user's consent.
IIRC, the user woud get an access token from Facebook for the target site, e.g. Vidio. Vidio then has to make sure that not only is the token actually signed by Facebook, but also that Vidio is the token's audience. The token shouldn't be opaque to Vidio.
I'm in no way an authority on OAuth, but I think the "opaque token" you're talking about is in the situation when Vidio would want to access Facebook on the user's behalf: they would get an access token for Facebook. The token can be opaque to Vidio.
Not being a full expert on it either, that flow is roughly right. Vidio does have to validate it. The entire pkce flow is bloody painful to implement, and it's actually quite hard to do it perfectly, and easy to accidentally skip a part of it and have it seemingly work ok.
What am I missing here? How do you mess this up? Like you redirect to the OAuth provider with a url that has your client_id and ask for a postback and you get it with a code, you use that code plus your app's client_id/secret to get user-creds and then query some API to figure out who just authed. The code is
tied to your client_id/secret. You have to implicitly verify the code you got just to figure out who logged in.
What you describe is the explicit grant flow and the exchange of the code for an access token automatically protects against this vulnerability. The bugs here are in the implicit grant flow where the redirect doesn't include an opaque code but an actual access token. There the application is responsible for validating that the token is for that application, but there's nothing that requires it to do that.
I'm a little confused about this. The article seems to say that the application (the server that is the target of the attack) makes a call to FB where the application believes that a successful response indicates that the user's credentials are valid. It says that FB responds with success (and the user's email). But in fact FB would respond with success regardless of whether the supplied token was valid (for that application). This seems like either a serious bug in FB's implementation, or very bad API design. Presumably it's the latter because FB is responding correctly to a request that has nothing to do with auth -- application (server) asked for the email for one of its users, providing its (the server's) credentials for that request. The problem is that the API doc/design has fooled the application developer into believing that that call was an auth check. But it wasn't.
Is that it?
Also...why is this not wrapped in a library such that it wouldn't be possible to make such a mistake?
"Facebook" (or whoever) just sends a token that says the user authorized the use of its account for the application. What happens in this 'hack' is that the token is valid, but was not actually intended for that application, but for another one (e.g. a malicious one, that is now trying to reuse the token to access other services of the same Facebook user). I don't know how Facebook tokens look like, but JWTs have a property called aud/audience indicating who the token was intended for. The application developer must verify that the token was actually intended for their application. That is my understanding, sorry if I got it wrong.
I get that. You're not wrong. I'm asking why Facebook tells the application anything other than "nope, that's not valid for you"? It doesn't seem to be a case of the application will accept random noise as a valid token. It does send the token to Facebook and Facebook responds with something that looks like "OK". But when they said "OK" they really meant "we gave you some valid information about the target user, but forgot to mention that the token you provided was not valid".
I'm saying that Facebook should in all cases respond with "Nope, that's not a valid token". Their API just shouldn't have a way to get data on a user without providing a valid token (by valid, we mean valid for that purpose, not stolen from somewhere).
So you send the token only and the token is valid, and there's no way for Facebook to check if it was meant for you or not. As mentioned in other posts, this would not happen in other flows because you would send e.g. your client secret along with the code.
Failing sounds like its a requirement for them to do it, but this is not true. If you send them just a token, then how would they associate it with the application? Like I said, in another flow where you send your client secret and a code to exchange for the token, they need to verify it and it would be a failure on their part if they didn't.
They have the client id when the token is requested, so they can associate the generated token with it by either keeping some state or encoding it in the token, no?
An attacker could call the post back with a code generated for another client_id. When you use the code to query who logged in, Facebook will still respond, even if the code was not generated for your client_id
This shouldn't be possible as the server-to-server request to Facebook to exchange the Authorization Code for an Access Token requires the client_id and client_secret to be provided. Facebook should (though I haven't actually confirmed this) verify that the code was issued to the given client_id. If the code was issued for client 123, when client 456 tries to exchange it for an Access Token Facebook should throw an error.
> OAuth also uses an “explicit grant type,” which is similar to “implicit grant type” but the server (randomsite.com) receives a code instead of a token and needs to make an additional request to Facebook to exchange it for a token. We’re using the implicit grant type example here because it is easier to understand and relevant to this post.
Are the first two attacks possible with explicit grant ? Wouldn't the auth server return an error if the client tried to request the access token with their secret and the code from another client ?
No, they’re not possible.
Yes, the auth server would return an error because the token is retrieved by the server out-of-band (Mallory can’t intercept via the browser) and must provide its credentials to Facebook to retrieve said token.
Additionally, Facebook SHOULD check that the client using the code is the same client that initiated the OAuth flow.
I recently implemented some code to: Sign into an account using username/password/MFA, authenticate with an API using OAuth to get a SAML assertion, authenticate with another API using the SAML assertion to get a token, then sign into an API using the token...
Not having to deal with auth yourself makes your life so much easier.
For my self hosted stuff, I've out a simple OIDC requirement in my Apache config (https://github.com/OpenIDC/mod_auth_openidc) and have set up a plain Keycloak server (should've gone with something simpler but oh well). Apache now sets the user ID in a header (that doesn't get passed on from the client for obvious security reasons) and all the application on the other end needs to do is look up the user ID. Multi factor authentication, password recovery, session expiration and everything else is all handled by Keycloak and no unauthenticated requests can make it through Apache. Tokens are signed and verified, of course, so there's no faking anything.
I think Caddy has a similar system built in, including a login page and everything. Give or six lines of config are all you need to handle authentication, plus you get automatic security updates for your authentication flow in case something happens.
There's a reason Okta and its competitors are selling auth as a service: doing auth yourself properly is a massive pain that you need to stay on top of. You need to set up secure hashes, integrate TOTP/FIDO2 with appropriate recovery mechanisms, set up login flows, deal with session expiration, and all that stuff, when all you want is "give me a user ID so I know what data to load".
These systems are annoying to develop against (though plenty of libraries are available to do the hard parts for you) but they're great if you just want to secure a backend or web application.
I don't know why you needed two extra assertions when you were already authenticated, though. That's sounds like bad integration to me.
Signing into AWS API through OKTA using a SAML Identity Provider. Just the way it was implemented unfortunately. OKTA's API is okay for this sort of stuff, but they need a SAML Assertion API that doesn't require scraping HTML. Also it would be nice if their JSON formatting weren't so all over the place.
So it went:
OKTA Assertion -> OKTA SAML Assertion -> AWS STS Token
Hang on... So this means that when I check the token as per the guide at https://developers.facebook.com/docs/facebook-login/guides/a... , it returns the information regardless of the access_token parameter matching the app id or not? My assumption without reading the docs would have been that if the input_token was from another app, FB would refuse to debug it if the access_token was not associated with the app id.
What's the point in asking for the access_token at all for that endpoint otherwise?
Am I misunderstanding the bug? This feels like a foot gun that was happily handed over to a lot of people all this time.
You probably dont need OAuth2/OIDC.
While the two are very powerful protocols when used correctly, and have many advantages and use cases, the truth is that they are not always necessary. In fact, you most likely do not need them.
I don't disagree, but I want to expand upon your point in a cautionary direction: Using OIDC/OAuth2 does prevent you from making several classes of rudimentary mistakes, entirely for free. The nonces and whitelists aren't there just to look pretty!
This article is not as impressive as one might imagine - the exploit appears to use the implicit grant flow, which is officially deprecated and should be replaced by the authorization_code + PKCE flow.
I read a lot of things in oauth, implemented several Saas/API access for server to server communication, and I feel it's so insecure that I am sure I have missed something...
Or you can just use JWT which instead of asking the identity provider whether a token is valid simply requires you to check its signature against a public key.
Some of the vulnerabilities in the article are due to validating the signature of the JWT properly but not checking the "aud" or audience claim.
Honestly, I would be tempted to call back to check the token due to how easy it is to make a mistake with JWT.
Of course, it just checks that the token was indeed generated by the identity provider. You still need to check that its contents match what you expect for your application.
Issuing a request to another server from my backend would be a non-starter, completely incompatible with any sensible performance or security model.
> it just checks that the token was indeed generated by the identity provider.
This! I have given a talk on JWTs dozens of times and always emphasize that you must do two things:
* verify the signature
* validate the claims (including standard ones like `aud` and non-standard, business specific ones)
You must must must do both to securely trust the token.
This isn't just a JWT thing, either. If you provide a token to an introspect endpoint like what Facebook provides, only the first item is taken care of. You must still inspect the claims.
Libraries will sometimes help with the standard claims, but you're on your own for non-standard ones.
> But this seems to require access to a different oauth token already
The article explans quite well how that works. The attacker makes an outwardly legitimate service, which is registered at the OAuth authentication provider, and waits for people to access it. All it has to do, is store the tokens on its backend, and try them, with the same username, against other services, using the same auth provider.
The important thing with PKCE is that it's not completely secure, either. A malicious actor can create an app that uses your client_id and its own code_challenge and verifier. In the event that there are any issues with redirect jacking, such as may be the case with custom schemes on mobile, you're hosed. The only way around this is to use intents (on Android) and OS pinning in the client configuration of your authorization server.
Another option is pkce spa, if they did not do 'auth' checks that the jwt token was indeed signed by auth0 or similar, a carefully crafted js alteration would let you take control of the front end. Then you could give it a incorrectly signed token with all the correct details for another user. Usually they would only use the email for matching which makes things even more trivial.
You would hope they verified the signing of the jwt token on the backend, but seems thats too difficult for many dev's.
PKCE should only be necessary if you're using app linking or have some client app in-between. If you completely trust the server than implicit is fine.
These days with a lot of non-standards-compliant OAuth implementations behind me, my conclusion is a lot of it is a giant waste of time, and it often adds completely pointless complexity for many of the cases it's used for. And as shown in the article, all the complexity often adds exploit vectors.
For 90% of API use cases just issue a revokable Bearer Token and be done with it. You are not an app platform!!!
[Qualifier: the EV API's I experimented with were a good use-case for OAuth, as they were user-first, based on mobile apps authenticated with user/password credentials. But I've worked with a lot of third party vendor API's where it was purely server-to-server access, but for some reason wrapped in OAuth... because... architects and compliance something something]