Hacker News new | past | comments | ask | show | jobs | submit login
Why is OAuth still hard in 2023? (nango.dev)
642 points by bastienbeurier on April 26, 2023 | hide | past | favorite | 282 comments



It'd be interesting to hear about people who have had a good time implementing OAuth, as my experience is similar to that in the article. I've played with adding it to a few side projects and the process usually goes:

1. Read loads of docs, end up pretty confused

2. Find a library that seems to do what I want

3. Install this huge library full of opaque code doing...things

4. Have an impossible time troubleshooting issues

5. Get scared ("I'm almost certainly doing something wrong here") and give up

I find it hard to have much faith in security standards like this. I want them small, well defined and understandable and OAuth does not meet any of these criteria in my experience.


I have read many docs.

The one that I find easiest to understanding is still the one that I wrote about a decade ago when I first had to work with OAuth 2. All others I understanding by mapping what they said to concepts in mine, and that seems to work pretty well.

My document is available at https://metacpan.org/dist/LWP-Authen-OAuth2/view/lib/LWP/Aut.... Even though you're unlikely to ever use that library or language, you may find it worthwhile.


As a fan of Perl, this situation of having an obscure, meticulously-documented CPAN module to not only explain but implement some common-but-surprisingly-hard pattern is just so ... Perl-y (:

Perhaps it's a semi-consequence of Perl being perceived as old and crusty, and popular with old crusty people who are merely trying to get things done without fluff (at least speaking for myself).

Reminds me of Data::Manip, another favorite of mine.

Thanks for taking the time to write this (and implement all that nonsense).


Thanks.

The crustiness in this case was enhanced by my grumpiness over some frustrating debugging sessions, and having to go through a lot of very confusing documentation to get there. That grumpiness shows through in a couple of places.

For example I understand why Google decided that I was asking for the same permission too often, and began handing me codes that would not work. And also wouldn't explain what was wrong with the code that I just got.


I'm pretty astounded, because this is by far the best OAuth 2 doc I've ever read, and that's after reading and watching many hours of content from leaders and companies in the space.


Thank you.

It summarized my understanding after a month of reading documentation, doing experiments, and generally banging my head against the wall.


Your documentation is amongst the best I've seen on OAuth, but it suffers from the same naming confusion I always run into when I'm reading OAuth docs.

> OAuth 2 makes it easy for large service providers to write many APIs that users can securely authorize third party consumers

If I'm trying to write a Mastodon client, I'm reading this line piece by piece:

> OAuth 2 makes it easy for large service providers

"service providers" OK, that must be the Mastodon service.

> ... to write many APIs that users

"users" That must be me

> ... can securely authorize third party consumers

"consumers" OK... that's also me?


That is why there is a terminology section: https://metacpan.org/dist/LWP-Authen-OAuth2/view/lib/LWP/Aut...

If you're trying to write a Mastadon client, then the Mastadon service is the service provider, you are the consumer, and the people who wish to use your client are the users.


I think it's helpful to not stray too far away from the standard terminology. I found that "third party" was an excellent indicator that the consumer is probably not me, especially since I've already qualified as a user.


Users are your users shared with the users of that API service (which may include you if you use your own service), consumer in this case is you (the application you're running).


Read the introduction again, and then follow the link to the Terminology section.


I'm bookmarking this to go through the next time I need to bang my head against this wall, thanks


This is the first Oauth document I've seen with a no-nonsense "Terminology" section. Thank you!

The only thing I'd suggest is putting the terminology first, so readers can first correct their misconceptions from all the terrible literature around this.


I thought about it, and settled on the compromise of referring to it up front, and then putting it at the end.

That's because there are many ways you could use a document like that. And the Terminology section is very overwhelming unless your goal is to understand how OAuth 2 REALLY works, and why it works that way.


Looks great, taking a look and favoriting.

(Only feedback I can think of is that it would be great to have a few diagrams... like from excalidraw or something. Am largely a visual learner.)


Please publish this on its own somewhere! This is fantastic.


I've reimplemented oauth for the same provider (bungie.net) countless times. I think it might seem daunting, but when you break it down into the steps it's pretty simple:

- Send user to example.com

- Eventually they're sent back to yoursite.com with a ?code=abc

- Call example.com/OAuth to exchange ?code=abc for access token and refresh token

- You're done (for now)

- When access token expires, call example.com/OAuth to exchange refresh token for a new access token and refresh token

The tricky part is that a bunch is implementation specific, so memorising the above only gets you half way to implementing with another provider.


I have an application with this flow already implemented to authenticate a user.

But the application currently has it's own authorization policy in a flat file which organizes users into groups and gives groups permission to take certain actions, a simple RBAC. I'd like to refactor this so that the authorization is delegated to the OAuth server.

Ideally I want to be able to ask a keycloak/okta server "now that user X is authenticated, are they part of the group Y?".

Since this isn't a common use case and everything is so abstract it's been very difficult to find an obvious path forward.


I had bit similar need when implementing our own OAuth2 authorization server to be used by our own (three) web applications and several APIs related to them, all of which use the same backend to store users and API keys with differing permissions to logical entities our datamodel consists of. These permissions are somewhat naturally mapped to scopes, but the main issue is that single user or API key could have differing permission across multiple logical entities, so it's M-to-N representation (M logical entities, with N different scopes). So simple returned scopes would not suffice, as resource server needs to know which entity or entities the caller also has access to, according to scopes.

As I could not find any pre-existing good guidance on solving this, I ended up implementing it by having the resource servers (i.e. APIs) check the token using token introspection endpoint at the auth server (not the RFC version of introspection, just private one for now but I may add the RFC compliant version as well so it could be called by 3rd parties as we have not yet rolled out this OAuth2 based setup for our customers). Part of that return information from the introspection endpoint is this permission mapping between logical entities and scopes, so the resource server can know to which entities caller has which scopes. Of course, as our access tokens are not JWT and just opaque (encrypted) data, the introspection endpoint then need to decrypt it - just so it can quickly validate expiration etc. - and if valid, fetch a matching copy from the database that has more contextual information than what's encoded inside the client-supplied access token. Otherwise, the length would be prohibitive if token would be fully self-contained with these extra information.

Maybe this same kind of method could be used for your use case as well, encoding authorization policy adjacent to the access token in your backend, and looked up by app using it after it has received the token?


Yeah I don’t see what the big deal is. You don’t need to know every oauth flow by heart and most of the time you’re going to be using the authorization code flow anyway. There are tons of articles and comments on HN about much more complex things than oauth every day.


3b. realize that library is deprecated or hasn't been updated in many years and is either totally broken, or incompatible with something else you're using.


3c. Write your own "library" which encompasses the the 50 lines of code.

Adding the oauth calls into your app that already has a web server, job processing, and a db is way easier than integrating what ever weird stuff some random library does.


I've both had to acquire and validate[1] tokens from Azure AD, both Client Credentials[2] and Authorization Code[3] flows, and found Microsofts documentation quite good. I've also had to acquire tokens using certificate-based Client Credentials flow from another party.

Overall I found it fairly OK, it's a bit of a learning curve when you're just used to basic user/pass, but it's fine.

However the main issue is that when it's not working it's very opaque. This is even worse when trying to _use_ the tokens.

I spent many hours trying to figure out why my Client Credentials tokens wouldn't work for logging on to Office365's IMAP server, only to spend half an hour searching and finding some Microsoft community forum post saying it wasn't implemented yet... they only supported Authorization Code flow. This has been fixed they say, I'll know later today cause that just came up as a priority ticket...

All you get back is some "nope", with no way to figure out why.

[1]: https://learn.microsoft.com/en-us/azure/active-directory/dev...

[2]: https://learn.microsoft.com/en-us/azure/active-directory/dev...

[3]: https://learn.microsoft.com/en-us/azure/active-directory/dev...


Yeah, pretty much. We have a hidden iframe silently refreshing tokens.

Don't ask me why, I thought the whole point of OAuth was that example.com was allowed to access identity.com. Instead, example.com opens a hidden identity.com iframe and does what do I know?

At my old job, users could specify their own IdentityProvider for their instance, which added a whole layer of complexity.

Now we have found out that a different team has implemented their side of OAuth completely wrong: Their device flow ("Open in app and enter 56474") doesn't poll automatically. Instead, user have to click "try now" manually. The whole auth is lost when the device restarts.

I'm not surprised Microsoft lost control of Bing because they implemented OAuth wrong for one of the services displayed there.


When I stopped using libraries and implemented my own things got a lot less frustrating. Still a pain in the ass, but I didn’t feel like the errors came out of nowhere any more anyway.


Yeah I've just implemented it myself. Google, Microsoft and some other OIDC provider.

I abstracted it away behind a "token provider" interface, so I can just instantiate the right one for the job.

There's some incantations to get the request right, but I found most documentation decent so just follow that.

One service required I implemented RFC 8693 token exchange, which turned out to be trivial since I could represent it as one "token provider" instance wrapping another.

Failures can still be opaque though, especially when using the tokens.


I had a good experience when I went in to a project with the attitude that "Ok, I'm going to wire up auth0 in perfect accordance with how they want me to, absolutely from scratch with the most perfect clean official documentation/library (in my case the react auth0 sdk) conforming approach possible.

I spent a lot of time making sure I did everything as by the book as possible and , in the end, I had a good experience and felt confident in my implementation, and like I understood all the moving parts. I even got help in a SO thread from an auth0 employee.

Auth0 could have been anything, here... On the same project, I kind of did the same thing with vite/react/tailwind client and fast api backend where I touched every piece of it, made every decision, and made sure I was really confident. This was in my own little vacuum where a big part of the app was just creating my perfect little sandbox. Stuff gets way more complicated when you have to hustle to get stuff over the line or you're trying to cram so new auth implementation in to an 8 yr old app that's been walked around on by dozens of engineers slamming out slop to close a ticket.

(Note: I know auth0 is a little more on-rails than implementing auth0 without it. They tend to have pretty good guides if you're using current hotness like react or fastapi)


My experience is similar but tbh I feel it's mainly due to the fact that oauth is implementing an important security layer (authentication/authorization) and that's hard by definition. There are lots of steps and things you can't afford to overlook or you'll be vulnerable to multiple different attacks


It's interesting to hear that because IHMO one of the reasons OAuth and JWT took over the world is that you can base64 decode the tokens and see whats inside them, compared to Kerb or NTLM which you eventually learn to spot based on their binary headers or whatever (eg NTLM tokens in HTTP Headers always start with "TRIM" for some reason)

I get the problem though, many of the libraries are not great or simply difficult to use


I found doing a custom (without a library) OAuth integration for only one service relatively straightforward, i.e. when it’s not treated as a standard. For example for a plug-in exclusively for Shopify (random example, didn’t try it) you would just treat it as the rest of their API and write custom code.

In short it works when you can see it as a guideline instead of a standard.


OAuth specification was the best source for me. All provider docs are simplified and usually specific to the provider


It’s been a while since I’ve read the RFCs but I recall them being very accessible and clearly written.


I read so many docs when I was trying to implement OAuth and got more and more confused. This video was a huge help though for explaining all of the concepts:

https://www.youtube.com/watch?v=996OiexHze0


We really deserve a less over-engineered actual standard that has a very restricted feature set.

In practice, isn’t OAuth predominantly used to verify proof of email ownership? If so, why not just use magic links as sign up & sign in?

1. Sign in/up: Enter email (can be pre-filled by browser/app)

2. Click the email verification link or enter code if on different device.

3. Profit. No manual typing necessary, only clicks.

This is trivial to implement, and can be extended in the future with a simple standard for browsers/apps to automatically verify in the background (to avoid the tab-switching inconvenience in step 2). On iOS they auto-populate SMS codes in a similar fashion.

2FA can be out of scope, (many times not needed because email providers already have it). But if needed, it can be added as a second step after the email proof.

Please tell me what I’m missing. This seems, to me, like an excellent trade off between implementation simplicity, extensibility, user convenience and security.


Email and SMS are inherently insecure, and it would be a lot harder for whoever's on the other end to apply security practices (e.g. look at where logins are coming from, rate-limit authorization attempts). They can't pass extra metadata back (user's name/address/avatar/etc.) and they can't do fine-grained permissions (grant me access to this github repo but not that one, grant me read but not write, ...). Plus I don't want to have to switch apps a bunch, and may not have my email with me; doing it all in the browser is much nicer.

(On a side note, I'm constantly annoyed/frustrated that after about 20 years of development, authentication codes/apps and smartcode verification are starting to be almost as secure and usable as the HTTPS client certificate support that was built into every browser as far back as the '90s)


> Email and SMS are inherently insecure

But nevertheless widely used as de-facto identity, I assume because account recovery in case of lost credentials is paramount. At least 90% of my accounts would be stolen or lost, today, should I lose access to my email.

I don’t particularly like email, for many reasons. Especially that most people’s email can be blocked by a faceless corporation that suddenly bans you. That said, I think there are far worse options, such as endless iterations on proprietary and ad-hoc auth “standards”. At least to me, the technical challenges of hardening email seem far less intimidating than educating the public on a new system.

> apps and smartcode verification are starting to be almost as secure and usable as the HTTPS client certificate support

I agree that client certs are greatly under-utilized and poorly supported by eg reverse proxies, but how would they help here? What’s the user flow for non-technical Joe to acquire a client cert to pay his bills?


> But nevertheless widely used as de-facto identity, I assume because account recovery in case of lost credentials is paramount. At least 90% of my accounts would be stolen or lost, today, should I lose access to my email.

Sure, but that's your choice. If you want to make your Google account require 3 factor authentication and a 10 minute timeout, you can, and from the perspective of any site logging you in via OAuth from Google, nothing changes. Even if 90% of users are going to use email only, it's nice to not force everyone down to that lowest common denominator.

> I agree that client certs are greatly under-utilized and poorly supported by eg reverse proxies, but how would they help here? What’s the user flow for non-technical Joe to acquire a client cert to pay his bills?

I just feel that if we'd put half the effort people put into SMS 2FA, authenticator apps, password managers and all that into making a better UX for client certificates, we'd be further along. Ah well.


It would simply not handle pretty much any case that I have used OAuth2 to implement so far.

For example - login system that merged LDAP/Kerberos/client cert/long-lived application token authentication into single system, that also linked said authentication system into all applications in the network, including making it possible to login to AWS Console using Kerberos (that one was twisty to get running, not because of OAuth2 but because of how it is handled by AWS IAM).

Also, I have used it to link in MFA systems of different kinds (it was definitely easier side than industry standard of using Radius)

In addition, this proposed system requires that every app has ability to send emails, which honestly is less simple than it sounds, especially today when sending to arbitrary public emails.


For service accounts, email is clearly not the right choice. I don’t have experience with enterprise auth, are Kerberos etc not using company email for human identity?

> this proposed system requires that every app has ability to send emails, which honestly is less simple than it sounds

For humans and especially end-users of consumer services, my observation is that the elaborate auth dances are using email ownership as last resort anyway, ie for account recovery and/or a trusted 3p that has verified the email. So the thought is simply to make that flow more convenient. Perhaps this is misguided.


In case of OAuth2/OIDC, if I do not use external providers (like Google etc.), I can still deploy one of the many OAuth2/OIDC providers myself and centralise handling of user database this way.

This also means I have one place to support sending last resort emails

As for enterprise auth, a lot of places in fact do not use emails for identity. Sometimes there's more than one login id mapping to one identity (noticeable case - Kerberos/LDAP as done by Active Directory, where your login can come in email-style form and pre-AD form, and the email-style one doesn't have to correspond to an email)


And each provider you intend to support exponentially clutters up code in the frontend and your /authenticate endpoint in the backend. Imports for each one, IF/ELSE statements to handle each one.


Well, my experience is like install keycloak, configure some framework plugin (like Spring Security) and that's about it...

I have rough understanding of oauth but never had to dive in.


My preferred development style is

1) skimming for limited context

2) copying and pasting random code

3) trying it with print statements

4) getting stuck

5) repeating steps 1-3 until it works


Because the documentation is bad. Oauth is really simple:

Lets say you want to use google as an auth provider. You do this:

"Hey google who is this guy? I'm going to send them to google.com/oauth, send them back to example.com/oauth, and in the headers of the request include the word "Authorization: bearer" followed by a bunch of text"

Google says "Oh yeah I know that guy, here I'll send them back to where you said with a token"

Then later on you can take the token and say "Hey google, somebody gave me this token, who is it?"

That's pretty much it. You have to trust that google isn't lying to you, but that's kindof the point of oauth.

But that's never what the documentation says. It's always 10 pages long and the examples are like "here's a fully functioning python web server using flask and function decorators, oh the actual auth flow, which is really like 3 lines of code, is hidden inside of a library".

To people who write documentation: PLEASE for the love of god show me how to talk to your API both using your library, but also using something like urllib2 or requests or something.

Ideally the documentation is the absolute most minimal way of making the service work, and then adds more and more usefulness on top of that. I'm not going to judge you for writing bad code in an example. The example could practically be pseudocode for all I care. I just want to see generally how your API is supposed to work.

edit: yes, auth0, I am looking at you.


In my experience with OAuth, one of the principle issues is that it's less a protocol and more a skeleton of a protocol. Actually go to the core RFCs for OAuth, and you realize that basically everything is implementation-dependent. You somehow register with the provider as a client (out of band and completely implementation-dependent).

Then you ask them to log you in by sending a request to an implementation-dependent webpage, with implementation-dependent parameters (which ones are required are--you guessed it--implementation-dependent), telling them to redirect you to your page when you're done. Well, actually, that assumes you're building a website. If you're a desktop application, you'll do something else. If you're on an embedded system that can't open up a webpage, there's another option. And there's yet more flows. Which ones are supported by the provider? You guessed it, it's all implementation-dependent. Oh, and maybe you need to refresh tokens to login the future. When? If? You guessed it, implementation-dependent!

It makes writing a generic OAuth client library hard because there's basically no commonality there. Really, it makes me long for Kerberos as a much saner SSO system than OAuth.


> In my experience with OAuth, one of the principle issues is that it's less a protocol and more a skeleton of a protocol

OAuth supports lots of different scenarios. Many people when they say “OAuth” they are only thinking of one or two of those scenarios, and ignoring all the others which aren’t relevant to them personally-but may be relevant to someone else

I worked on a system where we had a micro-service which had a token from OAuth server 1, and it needed to exchange it for a token from OAuth server 2, and we needed some policies to decide whether that token exchange was allowed or not. And that’s totally a use case the OAuth RFCs support (there is even an RFC specifically on token exchange), but a person wanting to add a “login with Google” button to their website isn’t interested in anything remotely like that.

> Which ones are supported by the provider? You guessed it, it's all implementation-dependent.

Security needs vary widely from system to system. So they defined a protocol which supports many different scenarios. But if your application only needs two of them, why implement the other N? OTOH, for someone who actually needs one of those other scenarios, having it standardised makes their life easier. You can’t expect the protocol to tell you which scenario you have, that’s inherently “implementation-dependent”, to the point that calling it that is getting tautologous


> > In my experience with OAuth, one of the principle issues is that it's less a protocol and more a skeleton of a protocol

> OAuth supports lots of different scenarios.

But it's literally only the skeleton of a protocol ("framework" of a protocol, as the actual spec puts it). That important "Then later on you can take the token and say 'Hey google, somebody gave me this token, who is it?'" part is totally unspecified by OAuth. How do you validate tokens? (Either on the resource-server end, or on the client-end to know if trying to talk to a resource-server is a waste of time, or to know if your post-redirect-uri is getting spoofed):

- OAuth: implementation defined

- most implementations: use our library, which validates the token using undocumented logic

- OIDC: there's a user-info endpoint you can call to, but lots of OAuth authorization servers don't fully implement OIDC, and even if they do, that's a lot of extra round trips

- RFC7523: The token is a JWT, validate the signature and claims... but everyone issues RFC6750 "bearer" tokens, not RFC7523 "urn:ietf:params:oauth:grant-type:jwt-bearer" tokens. But if you just close your eyes and pretend that your 6750 opaque token is actually a 7523 JWT and parse+validate it as such, that'll work most of the time.

So yeah, OAuth is hard because there's no great generic library, because a core part of it is implementation-defined, so you have to either do it yourself or use a specific implementation's library, and none of those libraries work quite the same.

(In the above, and for 99% of folks, "OAuth" = RFC6749+RFC6750)


In all honesty, though, "I have to look at the provider's documentation to determine like 4 URLs to add to a configuration" is not that wild of a thing? How much friction is too much friction?

> How do you validate tokens? (Either on the resource-server end, or on the client-end to know if trying to talk to a resource-server is a waste of time, or to know if your post-redirect-uri is getting spoofed):

At the beginning of the "standard" OAuth flow you can pass in client state. So you generate a signed nonce. Remember, you are starting this flow, so you mix in some user data (avoids CSRF problems), and you are pointing to some HTTPS site.

This is not 100% perfect compared to "remote server has some signing" but it does allow purely local verification of tokens when they are first received, as you can stick stuff into the "state" request parameter (as anyone should be anyways to avoid trickery).


> In all honesty, though, "I have to look at the provider's documentation to determine like 4 URLs to add to a configuration" is not that wild of a thing? How much friction is too much friction?

If it's something dumb/simple like grabbing a JWKS URL and an "issuer" URL, and then doing JWT validation, sure. Maybe you need to make a weird bespoke request to some other endpoint. Maybe you need to implement some uncommon signature verification. It could be anything.

> At the beginning of the "standard" OAuth flow you can pass in client state. So you generate a signed nonce. Remember, you are starting this flow, so you mix in some user data (avoids CSRF problems), and you are pointing to some HTTPS site.

So you redirect the user's browser to the IDP, with the nonce in the URL. The user grabs that nonce from the URL bar, then manually navigates to your redirection-endpoint, putting that nonce and their own fabricated token in the URL.


...what?

The server generates the auth code and redirects the user agent to your callback. You exchange that code with the IDP (over HTTPS which yeah that's its own nest of wormy trust) to get back a token. They can't inject a token because you don't get the token from them, just the one time code. If it's opaque you introspect it to validate or you just validate the JWT signature after pulling the keys from the JWKS endpoint. Introspection is standardized and an RFC. The state param is just a fucking session identifier.

All these URLs are defined and provided via the .well-known/openid-configuration endpoint. If your IDP publishes that endpoint correctly, most OAuth2 client libraries Just Work (TM) when pointed at the IDP domain.

Do EITHER of you even use OAuth2 outside of just cargo culting something you found off GitHub?


My apologies, it's been a while and I was forgetting about the authorization-code exchange step. So yes, for the most part "client"s can treat the bearer token as opaque. But "resource server"s absolutely cannot.

> If it's opaque you introspect it to validate or you just validate the JWT signature after pulling the keys from the JWKS endpoint.

If it's a JWT, which it doesn't have to be. OIDC allows the token to either be an RFC 6750 opaque token, or an RFC 7523 JWT, and overwhelmingly implementations use a "bearer" (6750) token. But, most of the time it's a JWT, so as I said, closing your eyes and pretending it's a 7523 token works, and so then you can just pull the keys from the JWKS endpoint.

> Introspection is standardized and an RFC.

In my experience, it is exceedingly rare for an IDP to implement RFC 7662 token introspection

> All these URLs are defined and provided via the .well-known/openid-configuration endpoint.

Yeah, OIDC-discovery is pretty sweet. And if the IDP implements OIDC-discovery, then it probably implements OIDC-core, or at least enough of it that you can use the user-info endpoint, like I mentioned. But I've seen IDPs that don't.

Have you ever used OAuth2 outside of OIDC?


Oh, thank you for reminding me of the well-known endpoint, was having trouble finding it in the OAuth RFCs but I wonder if it's pulled in elsewhere.

> They can't inject a token because you don't get the token from them, just the one time code.

that's not correct in the most common flow? The most common flow involves the user agent providing the information via a GET, so they can theoretically provide a token.

The reason the state parameter is important is because without it, a malicious actor can make a link that goes directly to your system's "oauth step finalized" step, but with their credentials. (Pedantic attack: service has slack push notifications integration, through OAuth. Attacker creates link like https : //service/connect-slack-finalize?token=token-to-attackers-slack that victim clicks on. Without using state, the service will just take the token and stick it into some slack integration. now attacker's slack is getting messages for the victim's account on the service. Cookies mean that the victim's thing is accepted immediately).


.well-known/openid-configuration is specified as part of OpenID Connect (OIDC) Discovery[1]. OIDC is separate from and on-top-of OAuth2. The OIDC specs come through the OpenID Foundation, not through the IETF (so not RFCs). (Also, while what they specify is super useful, they aren't nearly as well written as RFCs tend to be :) )

[1]: https://openid.net/specs/openid-connect-discovery-1_0.html

> that's not correct in the most common flow?

No, he's right, I was misremembering (assuming "the most common" flow is the "authorization code" flow specified in RFC 6749 §4.1). The user-agent provides a one-time "authorization code" to the client via a GET, and then the client receives that "authorization code" and does its own POST to the IDP to exchange that "authorization code" for the final "access token".


This is me misusing the word "token". Access tokens are gotten via POST, but the one-time code is gotten via GET and, absent usage of things like the state parameter, can easily lead to malicious attacks.


That's something that isn't OAuth2 or your end point is accepting something insane.

Are you talking about the PKCE variant of authorization code flow which is what replaces implicit flows in native apps and SPAs? Because those use code_challenge and code_verifier fields, not the state field. If you're doing all that in the state field with signed nonces you really should move to PKCE.


It's to prevent CSRF attacks. The attacker writes their own client and does half of a login on their own end (getting an authorization-code, but not yet exchanging it for an access-token), and then tricks the end-user to navigate to //service/connect-slack-finalize?code=<attackers-code>&state=<whatever>. But with the state parameter, the client can check the state parameter against a session cookie that it set previously, and say "wait a minute, this is the conclusion of a login from a different browser". Depending on what all session-state the client is keeping track of, it may make sense to sign that state-parameter-nonce to avoid having to remember session state server-side; but the simple case would be to just check whether it == a cookie value.


Using [0] as a reference, I'm talking about Step 3. This is, in my experience, the "normal" way that people are setting up OAuth between 2 services, with a user going through the flow.

[1] includes info on this (see "flawed CSRF protection")

[0]: https://www.digitalocean.com/community/tutorials/an-introduc...

[1] https://portswigger.net/web-security/oauth


Aha! That makes sense! Yes that can be a problem. We exclusively use a single (our own) IdP so it's less important for us. But good to know as some future feature work will actually make this important.

Thanks!


One thing I could imagine here is a signature flow happening from the sender side where they sign the nonce + the token, to avoid forgery. I don't know what the signature validation looks like in that model though (if you request a public key from the provider, are you requesting that at every request flow? If you're not, now you're adding a request to every normal, non-malicious validation flow)

> Maybe you need to implement some uncommon signature verification. It could be anything.

So my feeling on this is that at the very least OAuth tends to be "well it's going to look like this, but there might be some tweaks".

I have set up OAuth verification with multiple services in the past. The social aspects and the business aspects have always been more complicated, but at least "it's OAuth" (much like "it's REST" for APIs) establishes a core understanding very quickly.

I'm open to saying that we should add more standard stuff to the flow, if it's optional then people who use oauth libraries will end up with a lot of this stuff by default. But I think the status quo is pretty nice, all things considered.


My apologies about "to know if your post-redirect-uri is getting spoofed" and then my example exploit; I was misremembering and forgetting the authorization-code step.

If you're only implementing a "client", then yeah, you can probably get away with never caring about inspecting the token. But if you're implementing a "resource server", then you'll need to, and it's all implementation-defined (but overwhelmingly that implementation is "it's a JWT").


Any friction whatsoever is too much friction. By default, any user should be able to use any provider for any service, with no prearrangement between the service and the provider.

Anything less than that predictably leads to the situation we have now where a very small number of very large providers control identity and authentication for way too fucking many things.


Yikes. As someone who is going to need to integrate OAuth using JWT into a Rails app to access an API in the nearish future... I now dread it.


> And that’s totally a use case the OAuth RFCs support (there is even an RFC specifically on token exchange), but a person wanting to add a “login with Google” button to their website isn’t interested in anything remotely like that.

That's because OAuth in the industry has been changed to only talk about authentication (AuthN) and not (or very lightly) authorization (AuthZ).

And, for the better, AuthZ is so use-case specific that bundling it together with AuthN is just asking for trouble.

Consider AWS and IAM permissions. How would you implement an IAM AuthZ policy system with OAuth? Would you actually want to?

AuthN is a relatively simple process which is why that flow through OAuth (and specifically OIDC) is fairly well trodden and defined. The OAuth Authz capabilities are infrequently used which is why you see them being so spotty.


huh? OAuth is literally a "delegated authorization framework"


It isn't. OAuth2 really did away with AuthZ and focused on AuthN, regardless of what anyone says.

You have scopes but even those outside of the OIDC scopes are wishy washy and meaningless outside of each implementation.


And this is why there's five "log in with X" buttons on every webapp, but no "tell us your IdP and we'll send you there"; let alone any kind of magical "we'll take your email address and check the DNS record of the domain to figure out who your IdP is, and then send you there."

Not because the "figuring out who to OAuth with from DNS" part would be hard to design; but because there's no way to "autodiscover" all those implementation-dependent details of how you're supposed to talk to an OAuth IdP.

Compare/contrast: SAML. SAML binding a webapp to an arbitrary IdPs just by punching in the IdP's SAML config endpoint URL? Works perfectly.


There's a single URL for OIDC IdPs too. It's not supported in every web app for the same reason SAML isn't: it's a two-way exchange to set up, not a one-way exchange. In OIDC (OAuth 2) you need to register a client first. In SAML you need to register a Service Provider first. Only after you've done those steps will you be able to plug the magic configuration in and have it work.

SAML is too limited to compete with OIDC/OAuth2 on the web, but it's got some advantages for enterprise authentication, so it won't be going anywhere.



As much as I love OIDC, it still doesn't handle out-of-band client registration


https://datatracker.ietf.org/doc/rfc7591/

The only reason an authorisation server would support dynamic client registration, however, is because it's meant to be pluggable as the back-end for an API integration suite like Kong Enterprise.

The solutions that are one big well-known IdP have no reason to want it, so Google Auth, Amazon Cognito, and similar all don't support it.

The solutions that are aimed at letting organisations be an IdP do have reason to offer it: it makes being an IdP easier, because you can stand up a client registration service without using vendor-specific interfaces.

No-one who operates an IdP wants arbitrary, uncontrolled public client registration. It'll get abused directly, and it'll enable further abuse of the systems the IdP is meant to protect.


Yes because embedded XMLDsig is great! I've never seen a SAML implementation that worked reliably across IDPs outside of Java, honestly.


This is my experience. It's hard because it's very complex. As a non-domain-expert it's hard to separate necessary complexity from unnecessary complexity, but it feels like there's a lot of the latter, plus enough gotchas in the former ("oh, you didn't specify an encryption algorithm that we support in your request, and our logs are terribly unhelpful for realizing that") to make it a giant pain.


Is Webauthn a better standard?


Not exactly. With webauthn the complexity lies in frontend - you have to explain how and why users should use this and what it is, how to manage enrolments and so on. Touch ID, Windows Hello and Passkeys might simplify this but it is still more complex to explain to less technical customers than, say, Face ID on an iPhone app to remember your login.

If you want an easy all-purpose login flow, sign in by clicking a link in email is still an easy fallback.

OAuth from multiple providers also has complexity - Auth0 makes it so you have to maintain separate databases for passwords if you want to support login via OAuth and login from password. You have to link accounts to login from multiple providers.

Logins are simply hard work no matter how you slice it. Eventually they will be easy but… I’m not holding my breath. :)


If you were generally comparing standards: yes. Documentation with it is a breeze.

If you're talking about a "replace one standard with the next" type of comparison, then no. You can read more about why here https://oauth.net/webauthn/


> Kerberos as a much saner SSO system than OAuth.

+1, it really solves nearly all the authn and federation problems. You still find KDC installations in places with large *nix footprints. Sometimes it's AD, sometimes it's MIT with a cross-realm trust.

It's incredibly flexible and transparent to the user. It's easy for sysadmins, and various service owners to implement as it's basically drop a keytab in place, and set an environment variable for many daemons and libraries.

IMO, the only reason it fell out of favor with the web crowd is there wasn't a gaggle of centralized providers that let them stand up services without thinking about the infrastructure. It wasn't packaged up nicely.


As a developer that needs to use Kerberos for authentication between hosted services, I can say that Kerberos is simply awful to debug. When you have a failed login (refuse to auth), it is so hard for the _average_ developer to debug. We have pages and pages of cookbook solutions to various problems. Some issues are purely due to Kerberos complexity, and other issues are due to each programming language and their implementation of Kerberos. Most developers have no idea how SPNEGO works (Kerberos auth over HTTPS). I spend a lot of time on these issues, and I only have a surface understanding of SPNEGO. Oh yeah, and there are many subtle issues of Microsoft Active Directory (AD) vs MIT Kerberos.


Kerberos just doesn't solve the same problem: it starts by giving your username and password to a fully trusted client.

OAuth was invented to avoid the need to do that. If I use some finance package and I'd like it to download my transactions from my bank, I'd really rather not have to give it my username and password, with the full access that grants including the ability to lock me out of my bank account and transfer my money. OAuth lets you instead grant permissions for that program to do something more limited, like just read your transaction history.

Kerberos was never popular with the web crowd because it's not capable of solving the problem of authorizing web applications. Before OAuth everywhere just asked for your username and password to log in on your behalf.

And the only reason Kerberos works well today is that it's always now a single-vendor solution, homogeneous across the deployment. It's an interoperability nightmare otherwise.


> Kerberos just doesn't solve the same problem: it starts by giving your username and password to a fully trusted client.

Not really. In practice, Kerberos means just loading the gssapi or sspi library, essentially using the user's login to already have the ticket-granting-ticket generated and usable for the subsequent tickets. As the client application, I never see the user's password; it's all handled for me by the OS.


The OS is the fully trusted client - but you can't repeat that trick for a web app running on a remote server.


> Kerberos just doesn't solve the same problem: it starts by giving your username and password to a fully trusted client.

That's incorrect. The client doesn't need to be trusted at all. There is also there anonymous auth, certs via pkinit (which give you yubiki and piv/cac cards as well), and otp.

> Kerberos was never popular with the web crowd because it's not capable of solving the problem of authorizing web applications.

In MS kerberos, group membership comes over with a TGT in the from of a PAC.

> Before OAuth everywhere just asked for your username and password to log in on your behalf.

I'm taking about kerberos, you'd use a TGT to log into that service.

> And the only reason Kerberos works well today is that it's always now a single-vendor solution, homogeneous across the deployment. It's an interoperability nightmare otherwise.

There are a lot of cross realm trusts between MIT/AD and Heimdall/AD, and they just work. It really isn't difficult. As I said in my original comment, it's just that it wasn't packaged up nicely. All the parts are there.


> In my experience with OAuth, one of the principle issues is that it's less a protocol and more a skeleton of a protocol.

Because it is, really.

OAuth (2.0) is really the backbone for OpenID.


OpenID Connect, specifically, where a lot of people mix those up and start looking at using OAuth for authentication. And if I'm not mistaken, logging out is implementation-dependent in OpenID Connect. There's some pseudo-standards but implementations vary in my experience. And OpenID Connect also has multiple flows for different kinds of applications. There's still a lot of confusion and complexity here.


I'm still salty about how keycloak changed their openid logout behavior, removed the old behavior in the new version when there are still a lot of oidc clients out there that still expect the old behavior. I have two inatances of keycloak using the same version and somehow both have different iodc logout behaviors. I think it's due to one instance was upgraded from older version and inherited the old behavior, but I can't get the other instance to use the old behavior (the flag mentioned in the docs didn't work) unless I downgrade the version first.


Can you elaborate on this a bit? My logout process through keycloak is through a hidden (back channel) url. As long as I hit that client url it will end the session. Applications, I find, have different behaviors. Gitea logs out the session, portainer just clears browser cookies but the session remains active

I’m using the same keycloak setup for almost 2 years now, with upgrades


I was referring to this one: https://keycloak.discourse.group/t/keycloak-redirect-uri-log...

There is a flag to restore the old behavior but it doesn't work in newer version. Strangely, an older instance of keycloak I run still uses the old behavior even after being upgraded to latest version, so this issue seems to only affect new instance only.


Yes, single log out is an ongoing nightmare. <Stares at Ping> As many here have said the size and range of use cases that OAuth and OIDC support is off its head. And that's with the big boys who have millions of users, throw in ${EveryCorp} that implements its own token server and bespoke implementation of the auth, well.. good luck to the AI trying to take over our jobs.


Dynamic client registration is standardised (https://www.rfc-editor.org/rfc/rfc7591.html), the authorization code flow is standardised (https://www.rfc-editor.org/rfc/rfc6749.html). The device authorization flow is also standardised (https://www.rfc-editor.org/rfc/rfc8628.html). The preferred desktop app authorization process is described in a best current practice document (https://www.rfc-editor.org/rfc/rfc8252.html). A metadata URL for auto-configuration of OAuth2 Authorization Servers is specified (https://www.rfc-editor.org/rfc/rfc8414.html). How to validate an access token, which also lets you know if you need to refresh it, is specified (https://www.rfc-editor.org/rfc/rfc7662.html via an API endpoint, https://www.rfc-editor.org/rfc/rfc9068.html for JWT tokens) - or just use it, and if it fails, refresh it.

It's fully specified. It's well implemented - you'd have to go out of your way to find an authorization server that doesn't do everything above, with the exception of dynamic client registration, because that's not intended for clients but rather for integration with developer portals and similar. Google Auth and Amazon Cognito don't support dynamic registration for third parties, eg, because if you're doing dynamic registration it'll be because you're operating your own AS - Okta, Auth0, and Keycloak all support it.

There's also plenty of good generic OAuth client libraries. Spring Security, the oauth2 crate for Rust, etc.


Just because the standards exist doesn't mean everybody follows them. Most and maybe all implementations have some crazy customisation. It makes the standards and docs almost worthless.


But that's hardly OAuth's fault. I can blame HTTP/2 cleartext because none of the websites I'm trying to access are working in my browser, but it's not reasonable to blame a generic protocol for not providing my niche when it's the implementations that are broken. Imagine complaining to the WHATWG because Microsoft decided to host an IRC server on port 80 and none of my browsers work.

In my experience, OAuth works great. Standard libraries Just Work, login Just Works, all you need is a URL (usually standardised) and maybe the configuration the service is expecting (i.e. the names of the permissions you're asking the user for). Usually, that's nothing special to set up. In some cases, particularly with huge "fuck you we're big enough to be the standard" vendors, there are some stupid hardcoded values and workarounds you need to deal with because they couldn't be bothered to use a library or document their flawed implementation.

I've set up a Keycloak server and authentication against it is super easy to set up. Copy-paste a domain, two generated tokens, and maybe a URL if .well-known discovery isn't implemented by the client, and that's it. Things can be easy, companies providing "OAuth" support just choose to make your life hard.


I think the problem with the spec is that it’s way too large to fully implement when you only need some secure token exchange, so everyone does only the stuff they need.

If the spec was smaller people would go all the way to be fully compliant since the marginal effort would be minimal.


For a desktop application could use the password flow[0], no need to involve a web browser. Yeah, sure, you're now handing keys to the kingdom to that application, but if it's native software it could also have installed a keylogger or used an embedded browser component and extracted the data from there.

[0] https://www.rfc-editor.org/rfc/rfc6749#section-4.3


OAuth isn't an SSO system :P


One reason is that the protocol itself is more complicated than you've described.

For example, Google won't give you a long-lived access token. You need a refresh token, and then you use that to retrieve access tokens, and continue doing that as they expire.

Why? I have not a flipping idea. Please, HN enlighten me how refresh/access token dichotomy improves the API.


It's really trying to solve a problem they created by statlessly handing out tokens instead of keeping session state on the backend - they have no way to revoke a token once issued, so long lived tokens are a liability. Solution? More complexity! Hand out very short lived tokens, along with a slightly longer lived refresh token, which only allows you get a new bearer/access token.


You're free to handle a billion qps in authentication requests if you want. I don't suggest it.


Greatly reducing hosting costs at the expense of making someone else do a little more work sounds like a win to me.


Don't the clients have to do a billion qps as their retrieve access/refresh tokens from their data stores?


Yeah, but it's a negligible rounding error of cpu time for each client.

Distributing this work ironically makes it much easier to centralize the authentication.


Don't they still require some state to be able t invalidate refresh tokens?


Yes, but checking refresh tokens will occur much less frequently than checking access tokens. So you can imagine, for example, access tokens being JWT, so they are cheap to check. But every so often you have to validate the refresh token against MySQL (or BigQuery or what-have-you), which is more expensive.


That's all clear to me. But technically there exists a method to revoke an issued token. It's just that long lived tokens mean potentially lots of them == increased storage cost. It would be pretty silly not to check for revocation. How would one implement logout otherwise? Relying on just clearing session cookies? What if I obtained those cookies using something else than a browser and I can hold on to the cookie jar? Not checking for revocation == doing it wrong.

The purpose of a refresh token to allow the app to short circuit the login process. Regardless of how long the token is issued for. It's perfectly okay to ignore refresh tokens altogether, if one wants to.


The whole point of access tokens is to not do expensive checks on every request. Signature checks out and isn't expired - you are free to go. This is a core design thing of OAuth, once access tokens are out the door they are very hard to stop, so only let them last for 5 or 10 mins and use refresh tokens to get new access tokens.

Refresh tokens are your chance to do all the expensive checks - maybe you are IP restricted or want to step up with MFA etc etc. Check revocation etc


Login state for the identity provider and for the client application are different. Cookies are a reasonable way to implement it. Cookies + session storage backend will allow you delete sessions on the server side if you are worried about users keeping themselves logged in (?).

Your app is also responsible for deciding who has access. The identity provider is just handling the “is this person who they say they are” part.

Checking for revocation is checking whether the IdP still thinks that token identifies the user correctly. I don’t think it’s universally true or desirable that refresh tokens get revoked on logout. I don’t necessarily want logging out of Gmail to log out of Spotify just because I used Google auth for both.


Very simple, actually. Access tokens are short-lived and are irrevocable - google services only check validity and expiration of those tokens.

Refresh tokens are more like session tokens/cookies - those get checked every time. At Google scale, checking it probably expensive, so they are using refresh tokens.

These aren't for end-user or development experience, those are for AS performance.


That just pushes the problem around, yes?

So now the client servers have store/check the lifetime of tokens rather than resource servers.

Does that actually improve OAuth as a whole?


Even without refresh tokens, clients need to check lifetime. If the token is opaque and the lifetime is unknown, it is no different from a session cookie.

> Does that actually improve OAuth as a whole?

Like I said, this is an improvement for whoever is validating tokens and only them. Refresh tokens are not hard to use, not sure what's the confusion here:

`token = token.is_expired ? refresh_token() : token`

That's all. Even in wordy rust, it doesn't take much:

https://docs.rs/yup-oauth2/6.5.1/src/yup_oauth2/authenticato...

Are you mad that, unlike with opaque token, you know ahead of time when it's expired for sure rather than when you got 401?


There's the issue of scale, but also the issue of reducing the scope of a compromise.

If a short-lived token gets leaked the damage is limited to the TTL of the short-lived token.

If you were to pass around the long-lived token you would need to do forensics on the entire life of the token to figure out how/if the credential was used.

Just think very pragmatically about the probability to keep a short-lived token secret across all the places it's being transmitted vs. keeping the single API that exchanges the refresh token for a short-lived token super secure.


> Why? I have not a flipping idea.

https://www.rfc-editor.org/rfc/rfc6749.html contains everything you might want to know about OAuth in great detail :)


Auth0 has a business incentive to have people think their core competency is more difficult than it is.


That drove me up the wall in Python so much - ALL the documentation just described how to put a massive library into a cookie cutter example and never explained how it's supposed to work so I could debug the darn thing.


Not sure if they still do this, but when I first started learning, front and center of the documentation was telling you how to do it in a mobile app, then JS SPA app. The use case of a backend API+JS/mobile frontend was buried. And it was all stupidly named. It wasn't blatant like 1) MOBILE APP FRONTEND 2) JS APP FRONTEND 3) API BACKEND + FRONTEND.

So for me, mobile wasn't relevant, SPA wasn't relevant, and my use case, the third one was hard to find.


Asp.Net authentication is 10 times worse in this regard.


when it comes to this kind of things there's really no way around it: you're supposed to read the RFCs:

- https://www.rfc-editor.org/rfc/rfc6749.html: The OAuth 2.0 Authorization Framework

- https://www.rfc-editor.org/rfc/rfc6750.html: The OAuth 2.0 Authorization Framework: Bearer Token Usage


As someone who is hired to sling out features as rapidly as possible, that’s not going to happen.

I mean, you might say “but you should” or “it would actually help” or “in an ideal world…” but it is still, realistically, not going to happen.


Well hopefully someone has taken the time to, or there will be nasty surprises

I certainly don't want people building security sensitive parts of an app to be slinging the features out.


> As someone who is hired to sling out features as rapidly as possible, that’s not going to happen.

you do you, i guess.

but that's where the source of truth about how oauth 2.0 works is.

the "why" you're looking for it's in there.


That doesn't explain what the library is trying to do and how it implements it though.


if the library is implementing oauth 2.0, that explains what the library is trying to do.

how it's implemented... well that's an implementation detail, that most often one couldn't understand without the domain knowledge (unless it's something "trivial" like an off-by-one in some string comparison or something like that).


Yes, that "domain knowledge" is kinda important when you're trying to debug a blob of code that results in random permission errors after OAuth request.

The "implementation details" are what we're tasked with implementing ;)


The difference is that the RFC covers everything, whereas OAuth providers choose a subset of what they actually want to support.

Search engine > Encyclopedia


Yeah -- I think you just described OpenID (not OAuth).

And to be honest, this is part of the problem. We use confusing (and sometimes conflicting) terminology to describe both authentication (identifying somebody) and authorization (making sure you have the right permissions to do something).

More information: https://stackoverflow.com/a/1087071/19020


I recently gave a talk on this and agree. While it was fun to learn, the difference between oauth and oidc isn't clear. Especially with what I've been referring to as oidc "wave 2" - machine to machine authentication without OAuth seemingly involved at all.

https://youtu.be/nW3xK6sh1Ck


My personal frustration with the docs is that very similar words are used in sequence and for both generic and specific meanings.

“The possessor of the bearer token is authorised to re-request a refresh token code cookie.” is only a very slight exaggeration.

I also want to slap the Microsoft employees that use six different similar terms for three GUIDs that look identically random. E.g.: “Client ID (Principal ID)”.

It’s maddening.


OAuth document writers love abstract, lofty nomenclature for everything.

I like what you’ve done here with your explanation - I can practically envision a couple (or three!) literal sock puppets giving me this perfectly adequate overview.


Except HTTP prohibits copying headers from redirect responses to redirected requests. That's why OIDC exists, to deal with that by abusing URI q-params.

I've an Internet-Draft somewhere to fix this by making it possible to indicate on a 3xx that this is for authentication (so copy Authorization: from redirect responses to redirected requests) and to allow 401s to have Location: headers that make it more like a 3xx, and 3xxs have WWW-Authenticate:, so you can negotiate between legacy HTTP auth and redirect-based authentication.


Does this meet your needs: https://fusionauth.io/docs/v1/tech/oauth/ (Curious because I work for them.)

I think the reason OAuth is so successful and prevalent is because it is so flexible. This let folks implement what they needed and not what they didn't.

Overspecifying things didn't help SAML, after all. There are still holes and unimplemented/incompatible options. Authentication and authorization, especially across systems and organizations, is hard.

Is there a simpler standard for third party delegated authorization?


I’m happy when someone explains something with words strung together into sentences, rather than code. Code is an example, an example is not an explanation.

Then your examples can be simple, rather than trying to be a complete explanation of everything.


I have seen a lot of people get caught up on things like the client_id, client_secret, redirect URL, and scopes. This is relevant when you have a lot of tenancies for your customers and want them to log in via some OAuth provider. Do you create a new client_id for every tenant? Should you distribute a client_secret? Did you properly configure the redirect? Proving identity is the easy part though. Are scopes too broad or too narrow to accomplish the app's goals? Are there unintended consequences? Those kinds of things can slow down development.


I completely agree. What I want 99% of the time is basically a Javadoc showing the methods, the inputs, and the outputs, and all of the types. And if there's a short summary and an example as supplemental material, that's great.

Unfortunately, developers seem to think their contrived examples are a stand-in for actually documenting the methods. I deal with a lot of Javascript libraries like this and it drives me insane.


I recently did an OAuth integration with an API that documented its OAuth endpoints like all its other endpoints: including a mention of OAuth in the description but giving explanations and examples with curl, just like it would if there were no standard.

It was way easier than anything else I've done with OAuth (except log in as a user).


This. Unfortunately, the problem is, the people making these tutorials don't know that OAuth is that simple. They just know how to use the library that the 3 lines of code are hidden within.


Somebody print this comment out, frame it, and mail it to every major API author.


1. because it's conceptually not an easy thing

2. because the standard was messed up (instead of specificing a protocol with _at most_ one clearly specified flow per use-case (you can use it for more then SSO/Cross App Auth) they specified something more like a framework to build your own standard, but still pretended it's a single protocol, but if you can't use the standard to "blindly" build a client which works without knowing anything about the vendor then it's not a protocol, at least not a complete one)

3. because the standard covers too much potential use cases

4. because vendors haven't yet converged their implementations enough and might never do so (time for OAuth 3 which just specified on specific OAuth2 flow implementation??)

Given that OAuth2 was started with clearly very different goals in mind and the main profiteers of the current situation are a few big companies like Google, Facebook and Microsoft I have heard people stating that OAuth2 being messed up was intentionally. _I don't believe so_, it more looks like a typical case of accidentally over engineering by trying to doing so much.

I still don't like the situation as OAuth2 was too some degree a coffin nail to the idea of generic SSO (i.e. you as a customer can freely choose a SSO provider when signing one) and that sucks really hard and if we had generic SSO passwords would be _way_ less of an issue today (imagine _any_ email provider could _easily_ also provide a SSO service for any side you can have an account with).


Related gripe: Your API doesn't always need OAuth. If I'm using your product as your customer and I directly want to leverage my own product data via your API then I shouldn't be forced to have to implement [your almost invariably non-standard and difficult] OAuth implementation. An API key should be an option or HMAC if the extra security is felt to be warranted, but not the absurdity of needing to go through a client flow to check the boxes to say yes I consent to share my own data with my own self for this API use that will never be seen or used by a 3rd-party.


> If I'm using your product as your customer and I directly want to leverage my own product data

Correct. Fortunately, in my personal experience many services offer exactly this (JIRA, Hubspot, Slack).

OAuth is only relevant where the resource owner and client are different parties. I.e. it's for third party clients, not second party clients.


OAuth client credential flow is this easy mode you ask for. It’s one call with a secret in it and back comes a token.


The problem is "client credential" flow is not always available... And even if it were it might not as easy as you claim. In particular if you want to have an access token working on several instances. Most implementation allows only one access token at once so you need to have a shared storage and synchronization just for that...

If you are developing an API please have mercy: provide something other than OAuth. For me, avoid OAuth unless there is 3 distinct parties.


I ran into that recently trying to connect a web shop we've built to an accounting platform. The web shop handles the entire checkout flow, but the owners also want a copy of the invoice stored in their accounting platform. The platform has an API. So our web shop can ping the accounting platform after an order is placed, and sent them a record of what was purchased? No. Because the API requires authorization. So the admin has to occasionally log into our website and hit a button to send invoices to the accounting platform. (At least we can send multiple invoices at once, so it doesn't require them to hit a button on each individual one.)


If you want to have password reset, OTP, 2FA, webauthn and so on, you probably want to use an external authentication provider. Integrating it with oidc/oauth is much easier, than doing all that stuff by yourself.


Not to mention needing to create awkward shared service accounts to impersonate.


this. im baffled how most platform that has user accounts does not allow provisioning of service accounts so users can easily access their data.


Probably because the part most of the world is actually interested in was later renamed 'Open ID Connect' and any attempt to research using the term OAuth will bring you nothing but incorrect, old, ad-hoc implementations and presentations by snake oil selling 'personalities' claiming they understand it.


And once you realize you actually want OpenID Connect and none of the OAuth 2.0 features, you stumble about the fact that OpenID is not what you want because they just share a name... Where was I again ???


Naw, the same applies to oidc. Especially in the browser.

The issue itself also sounds easy enough to implement that you might get suckered into rolling your own (you only need to do a redirect and a request every n-minutes) ... But it's surprisingly hard as there are a lot of edge cases with long running timers and potentially multiple browser tabs/windows etc.


> OAuth is a standard protocol. Right? And there are client libraries for OAuth 2.0 available in basically every programming language you can imagine.

Here lies another pain point of integrating OAuth flows: In my experience those client libraries are a lot better in following the RFCs than the authorization server implementations.

It can cause a great deal of pain using such client library with a botched authorization server. If you take a look at the issue trackers of some client libraries you see a lot of "Please make this library work with auth provider XYZ" reports to which the maintainers (rightfully but painfully) respond: "Won't fix! We're following the RFCs here, we can't deal with every crooked authorization server implementation. Get it fixed at the other side."

And then you have to take the decision if you want to patch the client library youself to make it work (with the risk that this patches break on updates of the client library) or if you roll your own client for that particular auth provider. This decision gets even more complex if you have to support multiple authorization servers. If it's only one auth provider the third option would be using a vendor specific client library, which may be as botched as the authorization server, but at least they can talk with each other.


OAuth sucks. It's one of the worst abstractions of our time. It's incredibly easy to implement some half assed spaghetti and call it "standard OAuth".


I suspect it’s either by design that it is this terrible and hard to deal with or larger auth companies have a vested interest in keeping it this terrible.


There is no grand conspiracy here, and the problems OAuth is trying to solve can be pretty messy regardless of the approach.

When the environment involves delegating authentication across service providers, dealing with browsers and native clients, mobile apps, etc, there will not be a simple solution.

This is made even more challenging by constantly evolving application and edge deployment architectures, each bringing with it a new element of complexity.

OAuth sucks, and I’m sure it could be replaced with something better, but that replacement will also suck and bear a passing resemblance to OAuth, because the underlying problem is a messy one that isn’t going away soon.


An abstraction that allows a landscape of experimentation and learning on an interrupt based general purpose machine? What a bunch of thoughtless assholes!


It doesn't suck, it's literally bare-minimum what you need in order to securely retrieve the token.

It's by far not THE worst, you're spouting total nonsense. What's THE worst is lack of attention, and one needs quite literally 10 minutes to read the RFC and understand it's fairly simple protocol with minimal number of parameters.

I'm sorry you had a hard time with OAuth, but have you ever thought the problem is in you and not the protocol? Reason I'm asking is because you declare it sucks but you are not providing any kind of alternative of how it would not suck.


> and one needs quite literally 10 minutes to read the RFC

rfc 6749 is 4259 words according to wc -l. You mean that it takes 10 minutes to carelessly skim it.

Never mind that there are several other OAuth RFCs.


You need 10 minutes of focused reading to understand what the purpose of the protocol is and you don't need all of the OAuth RFCs to implement the bare-minimum nor do you have to support all the grants available.

Most people I worked with don't understand the purpose of OAuth and that's what the 10 minutes should be invested into.

Or, you can, you know - nitpick and live in the world of gloom and doom where everything sucks.


For what it’s worth, I taught a series of classes/labs intended to help developers within my former employer’s ecosystem understand OAuth.

What I learned after delivering the content to hundreds of devs over the years: OAuth confuses people, and for completely understandable reasons. Most people think about auth in terms of usernames/passwords or API keys. The mental models associated with these do not transfer to OAuth, and there are enough moving parts with client and server-side interactions that someone not already steeped in the authn/authz space will have some catching up to do.

Forming an understanding of the basic concepts rarely happens in 10 minutes, and if that’s your experience, good on you. Most of us bang our head against the wall for a bit and some of us go on to teach classes about it.

Bottom line: OAuth is hard. That is not the same thing as the general concept being simple.


> Most people think about auth in terms of usernames/passwords or API keys.

OAuth is hard to explain here because it is not an authentication protocol, and without extensions has severe security flaws in being one. OpenID Connect is the most popular effort extending OAuth to also handle authentication.

But that doesn't change that the thing OAuth is most associated with is not what it actually sets out to accomplish.


Yeah, this misconception is at the heart of so much confusion, and far too many OAuth implementations were clearly borne of "well, the customer was demanding we support OAuth so we implemented it", and there was clearly no understanding of the distinction between authn and authz, or what use cases are actually getting solved.

I once worked with a vendor who provided an API and required all API clients to use OAuth with Auth Code Grant Flow. The API was designed to support backend automation use cases within the product, and could execute functionality behind the scenes headlessly. Auth Code Grant Flow requires interactive login by a user sitting at a keyboard, and they never stopped to consider that backend automation use cases are not going to work well if someone has to periodically execute an interactive auth flow. This wasn't a once-in-a-year occurrence since they enforced relatively short refresh token timeouts.

The degree of confusion and the prevalence of that confusion on this subject is quite high. Someone gets a task to "Implement OAuth", and they see that Auth Code Grant Flow is the most common, so that must be what we should implement.


> Forming an understanding of the basic concepts rarely happens in 10 minutes

It happens when you're working and have sufficient experience, knowledge and practice when it comes to web, programming and HTTP.

It's impractical, irresponsible and insane to have anyone other than a person with sufficient knowledge to work on application / API that supports OAuth and for person with proper expertise - the protocol is simple because it's literally the bare minimum needed to establish secure request/response pattern.

I work with SAML 2.0 and OAuth2/OIDC, I deal with programmers and other people within areas of API, authentication, authorization and it is true that majority of people I interacted with are having troubles understanding these protocols. However, what I can attest to is that they're also extremely underwhelming as people working in IT, with bad work habits and misplaced focus. It's impossible to explain or clear up the protocol to such audience because they lack much more when it comes to knowledge and discipline. But, I did interact with several brilliant people with far less experience compared to me who did get the purpose of this protocol and who implemented it successfully and have done so quite fast.

What the wall of text means is that where there's will - there's a way. You can't force knowledge into audience who can't or refuse to understand it, so there's no point in even trying to do it let alone draw conclusions how the protocol is hard, confusing or difficult when there's whole knowledge void in other pre-requisite areas.


> it is true that majority of people I interacted with are having troubles understanding these protocols. However, what I can attest to is that they're also extremely underwhelming as people working in IT, with bad work habits and misplaced focus. It's impossible to explain or clear up the protocol to such audience because they lack much more when it comes to knowledge and discipline.

In my experience, IT generalists are dealing with a thousand problems, and that has little to do with misplaced focus and more to do with the fact that management just spent $$$ on this new platform and needs it integrated with internal systems, and they're encountering this OAuth thing out of necessity.

Part of the reason my training materials were successful within that community was because they distilled all of the usual questions about how and why someone would use OAuth relative to the platform and presented essentially a menu of recommended options to be explored further depending on their use case.

And people found success with this approach because it helped them solve their very immediate and concrete problem while also explaining why the approach solves that problem.

> You can't force knowledge into audience who can't or refuse to understand it, so there's no point in even trying to do it let alone draw conclusions how the protocol is hard, confusing or difficult when there's whole knowledge void in other pre-requisite areas.

Different people have vastly different learning styles, and some people just don't do well if you hand them a dense specification. Working from concrete use cases can be far more productive since it meets users where they are and gives them a pathway to understanding how their use cases connects to the OAuth concepts.

In a perfect world, everyone who works on auth would be experts. In the world we have, quite a few people have no choice but to do their best to learn the subject matter to solve the problem at hand.

Unfortunately this also leads to quite a bit of confusion and some really problematic OAuth implementations.


Can you share any of your training materials?


Unfortunately I cannot as they all belong to that employer and there are some privacy implications to linking the content as-is, but I’ve been thinking about reframing the ideas behind the content as a more general intro to OAuth and publishing a series of vendor-agnostic blog posts and/or videos.


Because it's too flexible, and allows the OAuth provider to choose which features they support. That puts all the complexity on the client to support every possible implementation.


I never understood how Oauth as a user experience is supposed to be distinguishable from phishing.


Many authentication systems push all phishing mitigation onto the user, e.g. "check the lock" (doesn't do anything) or "check the address bar" (not realistic for most users, ignores the ability to register official-looking DNS names).

So the answer is - OAuth doesn't solve the phishing issues around authentication any more than any other sort of non-curated hyperlink on the web.

WebAuthn, mutual TLS and Kerberos are the systems where that authentication is bound to a DNS domain or communications channel. Password managers also can provide this, although there are security considerations there such as competing web extensions, and it is a mechanism that the server cannot vet for risk analysis.

Such phishing-resistant mechanisms raise the bar for a successful attack from someone sending out a creative email to something a lot closer to coordinated/state-sponsored attacks on internet infrastructure.


Password managers mitigate this. If the form doesn't offer to fill my saved password, that immediately raises red flags in my head. I immediately bail out or manually validate domains.


I've found that if I haven't updated Chrome then my password manager plugin stops working, which happens frequently enough that this would no longer raise red flags for me. Every layer of security conspires to defeat every other one.


We have a joke about this at Zapier -- don't be an oauth butt!

"We support standard oauth butttt..."


See "OAuth 2.0 and the Road to Hell" - Eran Hammer's resignation letter from his role as Oauth 2.0 spec lead author and editor:

https://gist.github.com/nckroy/dd2d4dfc86f7d13045ad715377b6a...


Example: What is the purpose of OAuth having specifications for redirect URI allowlist AND PKCE?

None. They are entirely duplicative features.

But ... reason, reason, reason ... they both exist, and are going to exist, forever.

EDIT: I should say, PKCE is a functional superset of redirect URI allowlist.


If one uses a redirect_uri with PKCE parameters to send a response a non-oauth endpoint, the AS is still acting as a redirector.

It's typically not a list. You typically will have one redirect uri (at least, one per AS), because thats the communication endpoint defined by your client. There are other parameters like state to remember what you wanted to do after you had an access token.

PKCE exists to bind the front-channel authorization request and back-channel token request together as being by the same client. There is otherwise no evidence that the two parts are by the same software instance, which causes other security issues.


The redirect_uri parameter is not a list.

I am referring to the redirect allowlist registered out-of-band by the client: https://datatracker.ietf.org/doc/html/rfc6749#section-2


right. I was speaking to registration of redirect URIs - most clients should only register one, because they have only one endpoint (for that AS at least) for handling authorization responses. You should not use redirect URI for deep links, for example - you should capture pending actions to take after authorization separately.

You could hypothetically register redirect URI for the DNS names of your individual client cluster nodes, but there's no guarantee an AS will let you register as many URI as you have cluster nodes - and you will have a failure case to handle if the cluster node goes down mid-authorization, and coordination if you need to grow said cluster.


That's not correct. There are a number of attacks that can be mitigated by both, but PKCE serves as a very effective defense in case an authorization code leaks to an attacker. Such a leak can be caused by a malicious script on the redirect URI, referer headers, system or firewall logs, mix-up attacks and other problems even when the redirect URIs are restricted.

There is a good reason why we mandate both redirect URI allowlisting AND PKCE in the OAuth Security BCP RFC draft. One learning from our discovery of mix-up attacks with "code injection" was that client authentication is not sufficient to prevent the misuse of authorization codes.


Your comment doesn't explain what attack can be mitigated by redirect allowlist but not PKCE.


For starters, without restrictions on the redirect URI, I (as the attacker) can just redirect a user to the authorization endpoint with a client ID of a trustworthy client, a redirect URI pointing to my server, and a PKCE challenge that I selected so that I know the PKCE verifier. The auth code will end up at my server and I can redeem it, giving me (instead of the trustworthy client) access to the user's resources. If the client is a confidential client, I can use a authorization code injection attack to redeem the code and work with the user's resource.


Correct me if I'm wrong, but what you're implying here is that PKCE and URI allowlist are the same feature because if you specify URI allowlist while initializing the flow - it somehow helps public clients to prove they're the apps that initialized the flow and are the ones who are finalizing it?

Could you please expand on that thought, I'm genuinely curious if you actually might be right or whether this assumption of yours is how we get security holes.


On iOS, two applications can register as handling the same callback URI scheme. Which one gets the callback is non-deterministic. Thus, it is possible for some other app to get the token. I don’t see how an allowlist would mitigate this.

With PKCE, the other app can still intercept the token, but the token is incomplete and useless.


My understanding is that PKCE allows you to more safely operate a client that does not have a client secret.


The React Native docs have a good writeup on how PKCE reduces vulnerabilities with deep linking & session hijacking - https://reactnative.dev/docs/security#authentication-and-dee...

PKCE also gives you nice assurances that the device finishing the flow is the same as the device that started it. Without PKCE, the classic client credentials flow risks login CSRF - https://support.detectify.com/support/solutions/articles/480... - which may or may not be an attack vector you care about.


> well yeah, but there was already state, but nobody used it for that

state was meant to be application state. The problem is that there _wasn't_ anything like PKCE in vanilla OAuth 2, so the client was told to overload state with protocol state with particular processing rules, rather than just its own application state.

With PKCE, the AS can see whether it is the same client instance and reject the request. The state workarounds meant the client had to implement the rejection, which many did not do.


It's not particularly hard, this article is just self-serving content marketing because their product is an OAuth wrapper.


Is there any good way to do OAuth on a headless system? I want to be able to run batch jobs without a browser involved. There's OAuth for devices but that has limited real world use.


Yes, multiple.

You can implement client credential mode - this means storing a credential and using it to acquire a token from OAuth2/OIDC provider, then using that token as Bearer Token in your API calls.

EDIT: I can add that I have implemented client credential mode in what was effectively raw PowerShell and similarly it can be done with curl from any shell script, even pretty dumb ones. Just do a single POST containing the necessary JSON structure to your OAuth2/OIDC provider, parse returned JSON to grab bearer token value, use said token value in header

  curl -H "Authorization: Bearer ${token}"

You can also implement any kind of authentication in your provider (or configure a 3rd party one) and make it accept it - then ensure that this authentication model is supported by your headless program when it receives a redirect to login page. For example I have implemented Kerberos 5 login this way - CLI program would connect to OIDC provider (keycloak), get offered HTTP Negotiate GSSAPI auth, perform it using users kerberos identity, get token, use that token to access AWS STS to acquire AWS token. Completely transparent to end user/service.


But let's tell the truth: this is NOT user-friendly. Compare to Negotiate.


Oh boy, Negotiate. The wonderful system that works easily only on Windows.

I love it though, I use it as part of OIDC flows, because then I don't need to directly implement it for every application, I just make them call to Keycloak for it.


It works on Unix, what are you talking about.


The most painful part of getting Kerberos logins working on web apps in one of my jobs was getting Firefox to accept Negotiate headers from our domains and actually perform GSSAPI exchange using users keytab.

Windows has it somewhat centralized and well supported across many applications (especially those that simply use system-provided HTML widget), and at least Chrome automatically picks up system-wide config.


Just use a reverse proxy. Envoy, Nginx, whatever.


The problematic part wasn't handling Negotiate w/ GSSAPI on server side, it was handling Negotiate w/ GSSAPI on client side.

On keycloak side, it took a small configuration change (provide it with keytab, pretty much). By also using OIDC with Keycloak, I could ensure that access based on LDAP group membership was checked before accessing the application.


Hmm? All browsers, and curl, support Negotiate. There's support in many HTTP libraries for various languages as well. It's not universal though, that's for sure. At $WORK we have a [proprietary] client-side proxy to do Negotiate for apps that don't.


OAuth is typically meant for a user (resource owner) to delegate some authorizations on a protected resource to a client. If your headless system is trying to hit an API with authorizations granted by particular users, you'll need some way for the users to grant that access.

But the client credential flow assumes the client _is_ a resource owner and already has authorizations to do its business. Thats what you would typically use for pure system-to-system use cases, and basically amounts to "hit this endpoint periodically to get the current API key"


OAuth for devices was created for TV set top boxes and game consoles, network-connected devices with displays but limited password input. Device code flow is _widely_ used for this. To the user, this displays an activation URL or QR code.

You're looking for client credentials.


Client credential flow. It’s one call with a secret in it, and back comes the Access Token. If you want better security, it’s one call with a JWT signed by a private key. No browser, really easy to implement.


A while ago I wrote a small utility mailctl [1] to provide IMAP/SMTP clients (like msmtp, fdm, isync, mutt, etc.) with the capabilities of renewal and authorization of OAuth2 credentials. mailctl acts as a kind of smart password manager. In particular, access token renewal happens automatically in the background transparent to the user.

Since the program written in Haskell I also provided precompiled binaries to spare my potential users, most of them not Haskellers, from the complications of compiling it. The program have become moderately popular. However, little I knew how big can of worms I opened by releasing it to the public.

As many others pointed out, the two main reasons for the difficulties with OAuth are 1) the OAuth "standard" is terrible complex 2) the service providers' API documentation is an impenetrable obscure mess, always one step behind of the current working system. I have the feeling that the second one is not just negligence but might also be an intentional hidden lock-in mechanism by forcing the use of the vendors' ever changing API libraries.

[1] https://sr.ht/~petrus/mailctl/ also mirrored at https://github.com/pdobsan/mailctl


Love this post by Nango, it's exactly right!

Agreed that the biggest reason is that OAuth is a framework and not a protocol. That means different companies can do it ever so slightly differently, and as a dev, you need to understand the differences and normalize it for your use case.

At Stytch we've run into the same issues, normalization problems, security implications, flaky IdPs; a hard problem, but one you _have_ to get right!


> OAuth is a framework and not a protocol

Indeed, a lot of people do not realize the difference. Frameworks do not provide interoperability, and at most we can push to try to get people to solve problems the same way.

Profiles (such as OpenID Connect or FAPI) constrain frameworks allowing implementations to be interoperable.

OAuth is somewhat like the multitude of different dialects in some places like the United Kingdom.

The original article was talking about the problems due to trying to support all those different OAuth dialects. I'd argue most developers will never know that degree of pain, because they are only trying to solve problems within their particular application space.


OAuth is such a pain in the ass for small projects. I miss api keys.


Because "OAuth" is not a thing, it's a label you can glue on any wild-and-wacky authentication mechanism someone hacks together. It is structurally impossible for OAuth to ever become "easy".


Yep. OAuth is more of a concept than a standard. There are common patterns, and you might get lucky and find 2 integrations that handle it the same way... but I wouldn't bet on it. I worked on a system that OAuth'd with about 5 different 3rd party systems and each one was a little bit different.


I agree and disagree. The OAuth 2 spec is pretty explicit. People do not follow it.

The industry has played fast and loose with the spec because too many people implementing OAuth support in their app do not understand OAuth and/or insist on bleeding application or architecture-specific behavior into their authentication flow.

The end result is what you describe: a proliferation of approaches that roughly follow the spec. But this should not be mistaken for the spec being loose IMO. Rather that the state of auth is abysmally non-standard and homegrown.

I owned the authentication stack for a large enterprise platform company, and worked directly with the biggest players on ensuring compatibility between our platform and their auth flows, and the typical deviations from spec are almost always unnecessary and duplicate something that was already possible.

To be fair, Auth Code flow gets pretty funky with browser redirects + backend calls and can be hard to grok at first.

The other major issue is that once some custom auth thing exists in production, it's never going away if there are enterprise customers relying on the behavior. I suspect that a lot of customized implementations were never meant to be long term solutions.


Yes, I’ve noticed that a lot of companies implement “OAuth” differently. I’ve found the only way to reliably implement it is to read each company’s docs and also test their API. Once you implement their flow and signing (or not signing) method, it’s really not that complicated.


I don't know why it's still hard, but I can tell you, from the perspective of trying to prototype something, I would love to not have to use it just to find out if this functionality I want to build is worthwhile. I don't want to have to create a client, get some keys, worry about refresh tokens, etc. I want to hit an endpoint with an easily accessible token and get some data.

Make it so that the token is only valid for 30 days or something and then it requires moving to OAuth. But for prototyping stuff with curl and bash scripts, it's a giant pain.


If you’re prototyping use postman to get the tokens for you then copy/paste them into your prototype.


My current project is the first where we decided to use "real proper" authN/authZ instead of a home-brew solution or a minimal solution like asp.net simple auth.

I found that all the off-the-shelf solutions were either easy but expensive or cheap/free but difficult. I failed to find that mythical cheap or free and also easy and fabulous. Eventually we settled on self-hosted KeyCloak as the best bang for the buck. It took a bit of effort to get there, but we are happy with the results.


I am in the middle of implementing an OAuth 2 server for my internet forum [0]. The last 10 or so commits are OAuth related. I read docs for about a month and now I feel like I can code it. One thing that confuses me is how it can work without a client secret (it's recommended to not use a client secret for SPAs and native smart phone apps).

[0] https://github.com/ferg1e/comment-castles


So make sure you are doing PKCE for public clients.

With that, and the redirect URL (and therefore trusting DNS), and the other browser security model stuff…. You’re in fairly good shape.

There’s newer standards coming like DPoP - but it’s probably not worth it yet.


https://archive.is/26yI3

This explainer is pretty good. Sorry for the archive link, the original seems to be down


Nice read from nango team. FWIW we are using nango/nango cloud in prod to support ~15 integrations.

We signed up before they had an admin dashboard and did everything over cli. It was easy then and even easier now. We have also contributed a couple of providers when they didn't have apis we wanted to use setup yet (Stripe, Zapier NLA). They have an open slack channel and we were able to get them shipped immediately.


I integrated my product using OAuth 2.0 with 3 other systems from 3 different companies. Each one required different code on my side. Not fun at all.


My various brushes with OAuth and OIDC and FAPI have been horrible. The spec is written poorly, everybody seems to have their own idea of how it should work, nobody seems to implement the same thing.

Add to that the various version of OAuth/OIDC/FAPI and no clear migration path laid out to upgrade and it's a basic nightmare.

The Australia CDR (Consumer Data Right) notionally supports FAPI but they have their own interpretation of what is right so you simply can't use an off-the-shelf library to do anything.

The only way to preserve any kind of inter-operability is to wait for some other sucker to release their interpretation of this standard and everybody else in the eco-system has to test against that and eventually call it good.

Add to that - I just can't see why it has to be that complicated. Sometimes you can request signed+encrypted tokens, sometimes not, you can query the provider to ask them what they support but there are holes and gray area defaults for algorithms and encryption schemes and the whole thing needs to be set fire to and abandoned.


My favourite are the appendices that amend the protocol to fix security issues that almost no API seems to implement properly. These can get quite long and are frequently amended with new drafts every year or two.

For example, most APIs don't implement PKCE in order to prevent injection attacks against the authorization code grant type.

Just keeping up with security practices is a full time job for teams.


I wrote my own OAuth2 implementation (OAuth 2.0 Authorization Code Grant, both client and server side). I share many of the frustrations mentioned in the article.

That said, my first thoughts when looking at the advertised service is that I would be adding a huge external dependency with all the associated problems. That's a big cost, and I don't mean just in monetary value. I now have to rely on the network, as well as an external company with a business model that might or might not work out for them. Said company is likely to get acquired or shut down, none of these outcomes are good for me. Also, external APIs are a pain to maintain (I found that on average an API gets completely rewritten once every 2-5 years, which if you have 5 external APIs means that you will be rewriting one every year).

Also, OAuth has an unpleasantly large interaction surface with the rest of the software.

As a counterpoint, if I could buy a good local OAuth2 library for Clojure (subscription is fine), I probably would.


Does Clojure's "buddy" not work for OAuth2 or something? Last I recall it worked well enough but not sure what your specific requirements are.

https://cljdoc.org/d/buddy/buddy-auth/3.0.1/doc/user-guide


My rant about OAuth: It is overly complex because it has too many features to satisfy each and every enterprise case.

For me who runs a simple website (no 3rd party login), I learned that OAuth2 is just not a good choice. JWE/JWT and samesite=strict plus HttpOnly cookies are reasonably simple, yet secure enough.


A simple website with no third party login nor needing access to a third party API on behalf of the user should never use Oauth to begin with. a standard session cookie works just fine in those situations (as you discovered).


Personally, I have transitioned away from these third party IdPs. I have turned to spinning up my own authentication and authorization stack using an open source ecosystem like "Ory".

With just a single helmfile, I can deploy the entire application stack on my local machine (authN, authZ, smtp server,app db, application). Gives me ease that I don't have to rely on a third party vendor just for testing simple app flows. Transitioning to production is a simple swap of DSNs (database connection strings, real smtp servers, ...). Also, no more burning money when running simple tests or performing load tests with dummy users when using an external/managed IdP.

Still need to experiment with how upgrading will work and migration of user data. But otherwise I am a happy user/developer.


If you have already evaluated what are your thoughts on Keycloak? How does it compare with your stack?


As others have said, the main problem is that 1) There is too much flexibility which means that "name" might be "firstname" or "first_name" somewhere, which is why you need so many different implementations and 2) It is very hard to distill the relevant parts from the spec which covers all parts with equal vigour!

We have the same issue with the UK Wiring Regulations, so they produced an "On Site Guide", which covers only the main parts of the main regulations and is about 10th the size. The same could be true of the OAuth spec.

To be fair, OpenId Connect partially addressed both issues by taking a subset of the spec (when used for authentication) and then nailed down what the properties should be and what encryption mechanisms are required to be implemented.


> 1) There is too much flexibility which means that "name" might be "firstname" or "first_name" somewhere, which is why you need so many different implementations

Names are funny because many implementations of standards make the mistake of presuming there's such a thing as a "first name" and a "last name" that make up a name. Or that the "last name" is the part you pass down to your family, and so on. If I see "first name" in any spec, I assume the spec is written solely for a small subsection of the population by someone who didn't put all that much thought into the spec. Not every service needs an email address, there's no telling what a valid username may look like, address specifications are basically plaintext strings even though many services pretend there's structure to them, and so on. Hell, most APIs authenticating against OAuth don't even need a username or user ID to function, all they need to provide is an access token.

Most fields have no business being standardised because they cannot be. OAuth is not a "log in with Facebook" standard, just like IPv4 isn't an "access Wikipedia" standard. If you host something that people authenticate through OAuth yet, you inevitably need to provide some kind of custom service properties.


It was designed by committee, and the internet adopts any shitty technology that a big player pushes.


If you're struggling with OAuth I recommend reading this article from Ory:

https://www.ory.sh/oauth2-openid-connect-do-you-need-use-cas...


OAuth is too abstract and that’s why it’s bad.

I’m not a backend guy, for a hobby php site I’m trying to get Oauth2 with gmail free smtp, and the token refresh part to run on its own and even with packages like Phpmailer there’s no simple working copy/paste examples, just loads and loads of bits and pieces and outdated examples and small print about Google changed this or that… I mean wtf is the point of having a package that is supposed to abstract OAuth if you still end up having to dig for hours and having to figure OAuth2’s flow in detail?

I’m so disgusted I figured maybe I’ll do the password with 2fa on that email acct and be done with it even though OAuth shpuld be the better option.


From my experience, the blame lands on documentation and consistency at the implementation level. What should be a fairly simple process (redirect -> receive an HTTP request w/ code to exchange -> perform an HTTP request to exchange code for token) is always made into some esoteric spaghetti.

I recently wasted hours implementing OAuth login for Bitbucket because their docs have bad links, bad explanation of params to pass, and bad explanation of endpoints to hit. I implemented Github and Gitlab just before in ~30 minutes total, so it really is a YMMV by provider problem.


OAuth is easy in 2023. The problem is that OAuth does essentially nothing of interest by itself. It's a way to pass scoped access tokens.

That's it. That's the end of the spec.

If you want to do anything with those tokens, you need to implement against other APIs the provider makes available. Unless all of those APIs are also standardized, it means implementing a bunch of stuff for each integration. And now things are no longer easy.


OAuth is a way to obtain scoped access tokens.

And if you read the article, you would have something of substance to contribute on that topic, whether it is easy or hard or otherwise.


Authentication is easy and solutions are more or less fungible, but there’s no obvious and easy way to do authorization. OAuth makes authentication pretty easy but all the complexity comes from authorization, which is adjacent to authentication but not directly related to it. Although it’s not OAuth’s job to help you with authorization, it’s an inevitable next step that leads to massively divergent approaches


GNAP is coming soon: https://oauth.net/gnap/


Tangentially related: what do you use today to make authn/z work for your web sites/applications where you only do first party login (i.e. you don't do social login etc.)? Create a `users` table in your database and implement the logic on your own? Or proxy to stuff like Ory Kratos and act as a translation layer through a network call?


Auth0.com


What I would like to highlight is that it's still quite an achievement to be able at all to authenticate users with dozens of different APIs. Could it be better? Sure. Could it be worse? So much. Just imagine everyone would be cooking completely there own thing, or there being fractions or competing "standards".


As a web dev next auth makes auth a breeze just add a provider and go (unaffiliated, just love how easy its made things).


Next Auth is brilliant. It just works.


It’s hard because the concepts of authentication and authorization are simple to abstract but hard to keep separate at a technical level. It’s actually super powerful once you futilely attempt to craft your own system that keeps the concerns separate.

Oauth is authz

But do get in “the door” you need OIDC + PKCE auth code grant.. or authn


What I don't understand is why all of these complicated SSO technologies like OAuth, OpenID Connect, SAML, LDAP, etc are integrated at the application layer at all. All these web applications should be using something like "Trusted Header SSO" [1], thus completely offloading authentication to the upstream reverse proxy (which does or should exist anyway) and then the whole problem vanishes at the level of the application. It's like the MxN problem that Language Server Protocol addresses, but here "M" is applications and "N" is authentication protocols. The fact that I have to hand over my administrator password to the grubby memory of random shoddy applications as they pass it over LDAP to authenticate me in my corporate "holy grail" SSO-everything environment is insane to me.

[1]: https://www.authelia.com/integration/trusted-header-sso/intr...


I don't understand.

For example: a CI service wants to write to a Slack channel.

The CI service produces a request and directs me, the channel owner, to Slack where I confirm the request, and then the CI service obtains the scoped access token.

How is this replaced by anything to do with an "upstream reverse proxy"?

EDIT: I think you are thinking of OAuth as SSO. It can be used for that, but that's not it's "true" purpose like it is for SAML.

---

> The fact that I have to hand over my administrator password as they pass it over LDAP to authenticate me

Yes, that is insane and why LDAP is generally no longer used for web applications.


OAuth isn’t an authentication protocol, it’s authorisation protocol (which attempts to solve nebulous authorisation delegation scenarios in an often overly generic and overly complicated way).


For your consideration, centralizing auth like that imposes the requirement of (a) running a second piece of security tech (b) ensuring that all upstream applications are in the same authn/authz domain as the front proxy

That's not even getting into the fact that the "SSO technologies" that you listed have vastly different mental models of what inputs and "claims" they support. Maybe you're hinting at https://xkcd.com/927/ but for better or worse I don't believe there is a magic wand authn/authz standard like you want


If you only want to put a dumb web app behind a proxy then use oauth-proxy. But OAuth caters for a lot more than that use case.


Given that this seems to describe a real problem, why hasn't anyone launched a proxy service that normalizes across the varying implementations?

Why should tens of thousands of developers have to discover the particular quirks of an implementation instead of doing it once and re-using the results?


That's what we do at http://nango.dev


They have. https://clerk.com/ is an example that I have been looking to use lately


oauth-proxy?


I'm not super familiar with all of it on a tech level, but is some of this because individual apps that shouldn't exist hoard it?

In my case it's Duo, literally the only 2FA thing I must use that doesn't let me have my dang token so I can use whatever app I want?


If you want to read the rfc’s I had chatgpt explain the parts of the authorization code flow and link where in the rfc’s the parts are defined.

https://bryce.groff.family/oauth-2-1/


Use fief ! By the creator of FastAPI


It's a turd, if you want poor security you should make things so complicated that half the implementers have no idea what they are doing. We should have a kind of LD50 for specs: How much feature cruft do you need for 50% of the test subjects to die.


Exactly right answer. It's a turd.


Because authentication and authorization is a hard problem in general.

Hard to make it right, hard to make it secure, hard to make it simple. And exponentially harder to make it versatile.

And if oauth is "hard", wait until you see OpenID and the myriad of extensions...


On point 1, that's why https://oauth.net/2.1/ is in the works, to make a smaller, clearer subset of things that folks need to read and implement


Somewhat offtopic:

HN shows me the name of the submitter, bastienbeurier, in the colour #cd6e00. (Not sure what people call that colour in natural language, as i'm colour-blind.)

What's up with that? The account was created in 2013.


No one implements it the same way.

Some require scopes, some don't.

Some require comma-separated scopes, some want an array.

Some require an HMAC signature, some don't.

Some want a JSON body, some want a urlencoded form.

Etc.

Etc.

Etc.

You're never implementing OAuth, you're implementing some company's bastardized flavor.


Because the underlying problem is hard. SAML is difficult too. Identify federation, across the public Internet, between disparate entities is a fundamentally difficult problem with no simple answers.


I found this tool useful for debugging Oauth: https://github.com/cloudentity/oauth2c



I've been using WorkOS for my SaaS products and I'm pretty satisfied with it. It's straightforward to set up Google/Microsoft/MagicLink (free), including staging and production environments. The best part, though, is that it lets my enterprise customers configure their own SAML/OpenID Connect IDPs.

I get charged per connection, so I just pass that cost onto my customers. As a solo developer, I'd have a hard time supporting so many IDPs with a unified API without this.

They did recently hike up the cost per connection, but they're giving a one-year extension at the old rates, which seems fair.


Their product looks great but the price seems insane. $125/m per connection? That's very hard to swallow for a SAAS looking to charge maybe $10-30 per seat. I get it, "enterprise!", but that's a pretty high barrier to entry for a company still establishing itself.


Absolutely, it's not the most inexpensive option out there. My strategy is, that I only initiate a connection once an agreement with an enterprise client is officially signed. This way, I'm not investing upfront without the assurance of a paying customer. I find this approach works quite well for my situation.


insane indeed. btn.social is 1 cent for 100 logins. no MAU shenanigans. won't bankrupt/drain most startups


I think sometimes OAuth providers explicitly explain it in a more complex way, just so that you would use their bespoke platform/services to implement it.


I don't know, I look at oauth and it seems like its insecurity by design. Huge complex spec, with lots of corner cases, and requires really complex functionality like HTTP and JS. All of which can hide obscure bugs. Its like no one has learned from history.

Frankly, I'm not sure what keeps people from simply putting fake Oauth screens on random phishing emails. Figure out what a companies default auth screen looks like, pop up a similar looking web page.. bam plaintext passwords that can be used to authenticate with real services.



Because most OAuth libraries are very incomplete.

They implement a few happy parths of the standard, but a lot of things are just not there.


Oauth is simple. OAuth2 is a protocol for making protocols and every mega-corp's implementation is different. It's requirement for email access in the mega-corp walled gardens breaks imap in the sense that just imap doesn't work anymore. And in the pragmatic sense that many smaller email clients don't and won't support all the various idiosyncratic implementations.


I HATED having to do anyting with oauth back when I did more web work. Think it actually gave me PTSD.


because it was a dumb standard with terrible implementations solving a problem that nobody had


Right. Do all of those dumb dialogs that allow you to sign in using your Google account, import data from one cloud to another, add integrations from third party providers to other apps… they are all solving problems you don’t have?

OAuth is one of that pieces of infrastructure that power lots of things without people even noticing them, but don’t bode well with armchair engineers.


I'm an application develop who uses oauth, but it's only one in many authentication systems I've used, and certainly one of the worst.


Which highlights the very problem I was talking about, considering OAuth is an authorization protocol, not one for authentication…


Which others have you used which solves similar problems, but better?

Genuinely curious.


Well, I use oauth only for authentication (IE, use it to get a trusted email address for the web user of my app), not authorization. Of all the systems I've used, kerberos has been the best authn solution, then basic auth to log in as a user where authorization is done in another ACL system (FS acls, for example).

My complaints are almost entirely about the ergonomics of writing oauth code in a server, provisioning the resources to make it work (like DNS), and then debugging 900 redirect_uri errors.


Because OAuth 2.0 sucks and got all bloated. The 1.0 version worked great for everybody but super big companies like google and facebook. But then big companies came in and made it a bloated standard that doesn't meet the needs of most users.


That's not entirely true. OAuth 1.0 and 2.0 didn't cover much, and everything was implementation specific. Both standards only cover authorization, while the majority of consumers wanted to use it as authentication.

Big companies had to roll out their own authentication layer on top. When OIDC came into play, it was already too late.


Not one mention of flickr in your post...Would have loved it.


Thank you for "asking" and have the luck of appearing in the first page in HN. I am sure many here wonder why. There are more complex pieces of software that find a way to abstract the problem in a nice interface.


If you use OAuth make sure to use stable sorting.


I can't seem to find an old article written on the early days of OAuth 2.0, praising OAuth 1.0a, because among other things it signed the URI parameters and because unlike OAuth 2.0 Bearer tokens, OAuth 1.0a didn't require sending credentials in the clear (this was at a time when HTTPS wasn't quite as ubiquitous, and OAuth 2.0 pretty requires TLS to be used securely).

As someone often working with OAuth 2.0 flows, I enjoyed the article and think that it raises many good points. I'd also say that many of them come from things that affect _any_ system solving a problem similar to OAuth 2.0, because authorisation is hard to get right, or from extensions to the protocol that really aren't OAuth 2.0's fault (like the `realmID` parameter, obviously added to make the life of those API developers easier at the expense of those actually trying to integrate with their systems).

To me though, I wholeheartedly agree with 'Problem 1: The OAuth standard is just too big and complex' and 'Problem 2: Everybody’s OAuth is different in subtle ways'. OAuth 2.0 is more of a framework or metastandard, and no API implementation uses all parts of it because they simply are not relevant to that API or use case. This alone makes it quite hard to 'simply use OAuth' for an integration, because a big part of the job is figuring out which parts are used and in which ways, even if everything is done per the RFCs.

By contrast, OAuth 1.0a was comparatively much simpler and focused on a more narrow problem. OAuth 2.0 allows you to convert a SAML claim from one provider into an OAuth 2.0 token for a different provider to then delegate permissions conditionally to another actor for a particular action on yet another API.

Are we better off with OAuth 2.0? I say yes, because figuring out the differences between providers is probably easier than realising a hundred completely different implementations that have very different ideas of what an authorisation or delegation flow should look like. I think that one can learn to reason about OAuth 2.0 and then apply this logic to integration jobs with slightly less cognitive load than a completely bespoke solution.

At the same time, I think something sorely needed is something like OAuth 2.0 profiles that standardise the features used to integrate with OAuth 2.0. Probably most social media sites have similar requirements, most auth-as-a-service have similar requirements and so on. Having a common subset of features and decisions for common use cases and scenarios would IMO greatly simplify integration tasks and, paired with choosing a good library, make it indeed possible to integrate with a random service in under an hour.

The thing is that some of that was the spirit of older standards like OAuth 1.0a and OpenID (not to be confused with the newer OpenID Connect, which is OAuth 2.0-based), and the world seems to have moved away from that, probably because of the flexibility that OAuth 2.0 affords and the want to tightly control authorisation and external integrations.


It's easy with https://btn.social/ :D

I made this because I was tired of asking the same question.

There are a bunch of services out there trying to address the same thing, but unfortunately they get wildly expensive and they own/control your user data.

Professionally, I've been a consultant and freelance developer for 10+ years. Time and time again I'd see teams start with Auth0/etc, only to eventually:

1. duplicate user data into their own DB/Cache to avoid the latency of asking Auth0 for user data

2. remove Auth0 entirely because having active users !== having paying users... so Auth0's monthly bill of $3000 to $10000+ couldn't be justified

3. #1 then eventually #2

btn.social is just as simple to set up, but has predictable & transparent pricing with volume discounts the more you use it. At its most expensive, it's 100 logins per penny ($0.01), which is to say that "MAUs" are a thing of the past.

As quick pricing comparison:

- Auth0's (public) pricing calculator [1] shows 10k MAUs for $228/mo. That just means 10k users logged in 1+ time(s) that month. In btn.social terms, that's 10k, 20k, or 30k logins depending on if each of those users logged in 1x, 2x, or 3x (etc). For $5, you get 50k logins with btn.social.... and for $169 you get 2.5M logins... [2]

- Supabase charges $25/mo, which includes a lot more than just OAuth(!) but if you look at Auth alone, that $25 includes 100k MAUs and is an additional $0.00325 per MAU thereafter [3]. Assuming 1x login per MAU, that's $2950/mo for 1M users. With btn's "Business" tier, you get 1M logins for $79/mo, but of course there's still the $169 for 2.5M if you don't like the "1x login per MAU" assumption.

Another key difference is that btn.social saves nothing about your users.

It's your app so you get to keep your data. You'll never need to have to ask btn.social for Alex's profile because we won't have it :D This means we're privacy-first & we don't add/pose a risk for GDPR concerns.

btn.social was just launched last week [4], so some additional guides & examples are still underway, but there are already 10+ OAuth providers [5] to choose from & a free/"Hobby" tier so anyone can start playing around in just a few minutes.

[1]: https://auth0.com/pricing

[2]: https://btn.social/#pricing

[3]: https://supabase.com/pricing#compare-plans

[4]: https://twitter.com/lukeed05/status/1648751062340501505

[5]: https://docs.btn.social/providers/


First off, congratulations on launching! I'm not sure why you're getting downvoted, because it seems quite relevant to the discussion. I'll definitely look at btn.social for my next hobby project. The pricing is super reasonable and the documentation very clear.

One question I have around OAuth in general is whether it only applies for SPAs or if I could also use it for more traditional multi-page apps? I guess I could do something similar to what you're doing in your guide [0] and do a POST from the onlogin callback, but how does my backend know that the payload is valid?

[0] https://docs.btn.social/guides/#link-a-payload-with-a-db-rec...


Thank you! Not sure, just part of the orange site mystery~

Your `onlogin` callback will only ever be invoked by the SDK on successful login. So the data coming thru there is always the result of the OAuth flow itself. For MPAs, you can create your own document cookie and/or modify local/sessionStorage, and then every new page load will pull/load from those storage systems. SPAs can & should do the same – they just have the added benefit of keeping in-memory state alive.

As for POSTing to your backend, the same XSRF/CSRF/CORS rules & mechanisms apply as with any other client->server transaction. You can rely on CORS for limiting who can talk to your API, you can pass along a unique token for CSRF prevention, and/or come up with your own Authorization header format.

Soon I'll be adding a `redirect_uri` config option for all btn.social apps. Much like the normal `redirect_uri` in OAuth, this will be a target URL that btn.social redirects users to post-login. This will enable server-side redirects, which means that your API just has to verify the "login.btn.social" referer.


Ah great, thanks! It would be great to see a larger example on your site to see how this all ties together. For example, I don't really understand what's stopping anyone from opening dev console and just calling my 'onlogin' callback and passing it fake data to impersonate another user. It seems to me that somewhere I'd still need to verify the user data with the provider.

I fully realize that that's probably due to my own ignorance and no knock on you. However, maybe there's others like me who'd benefit from some more examples.


For sure! There are some larger examples coming so that people can see it plugged into a more real-world application.

RE: onlogin, that method accepts a callback handler that is added to an internal/private array of callbacks to run once the `popup` or `redirect` sequence has finalized. It's the only externally-facing "doorway" into the OAuth flow. Everything else is inciting action (`popup()` and `redirect()`) or a read-out of the last-known payload (`last()`). It's actually very similar to a state manager I wrote a while back[1]. Callbacks are added via `$.on()` which assembles an internal `tree` map to be `dispatch`d once a change happens.

You've probably already seen the API docs for the btn.social SDK, but linking just in case[2]

[1]: https://github.com/lukeed/vegemite/blob/master/src/index.js

[2]: https://docs.btn.social/usage/#methods


Thanks!


It's some kind of misty cristal, requires high up, high up ... A few could really grasp that




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: