Why is OAuth still hard in 2023?
nango.dev
nango.dev
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.
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.
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.
It summarized my understanding after a month of reading documentation, doing experiments, and generally banging my head against the wall.
(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.)
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).
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.
> 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?
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.
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.
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.
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.
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.
> 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.
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)
(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)
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?
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.
In short it works when you can see it as a guideline instead of a standard.
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.
- 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.
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.
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?
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...
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)
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
I get the problem though, many of the libraries are not great or simply difficult to use
I have rough understanding of oauth but never had to dive in.
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.
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.
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.
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'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/
Because it is, really.
OAuth (2.0) is really the backbone for OpenID.
I’m using the same keycloak setup for almost 2 years now, with upgrades
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.
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.
https://swagger.io/docs/specification/authentication/openid-...
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.
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.
If the spec was smaller people would go all the way to be fully compliant since the marginal effort would be minimal.
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
> 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)
> 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).
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.
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?
> 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).
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.
[1] includes info on this (see "flawed CSRF protection")
[0]: https://www.digitalocean.com/community/tutorials/an-introduc...
Thanks!
[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".
> 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?
> 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.
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").
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.
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.
You have scopes but even those outside of the OIDC scopes are wishy washy and meaningless outside of each implementation.
+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.
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.
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.
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.
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.
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.
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.
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.
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
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.
Distributing this work ironically makes it much easier to centralize the authentication.
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.
So now the client servers have store/check the lifetime of tokens rather than resource servers.
Does that actually improve OAuth as a whole?
> 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?
https://www.rfc-editor.org/rfc/rfc6749.html contains everything you might want to know about OAuth in great detail :)
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.
- 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
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.
I certainly don't want people building security sensitive parts of an app to be slinging the features out.
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.
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).
The "implementation details" are what we're tasked with implementing ;)
Search engine > Encyclopedia
So for me, mobile wasn't relevant, SPA wasn't relevant, and my use case, the third one was hard to find.
Then your examples can be simple, rather than trying to be a complete explanation of everything.
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
“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.
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.
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 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.
It was way easier than anything else I've done with OAuth (except log in as a user).
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?
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).
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.
If you are developing an API please have mercy: provide something other than OAuth. For me, avoid OAuth unless there is 3 distinct parties.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
"We support standard oauth butttt..."
https://gist.github.com/nckroy/dd2d4dfc86f7d13045ad715377b6a...
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.
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.
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 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.
I am referring to the redirect allowlist registered out-of-band by the client: https://datatracker.ietf.org/doc/html/rfc6749#section-2
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.
With PKCE, the other app can still intercept the token, but the token is incomplete and useless.
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.
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.
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.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.
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.
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.
You're looking for client credentials.
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"
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
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!
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.
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.
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.
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.
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.
This explainer is pretty good. Sorry for the archive link, the original seems to be down
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.
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.
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.
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.
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.
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.
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.
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.
https://www.ory.sh/oauth2-openid-connect-do-you-need-use-cas...
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.
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.
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.
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.
Oauth is authz
But do get in “the door” you need OIDC + PKCE auth code grant.. or authn
[1]: https://www.authelia.com/integration/trusted-header-sso/intr...
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
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.
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?
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?
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...
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.
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.
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.
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.
They implement a few happy parths of the standard, but a lot of things are just not there.
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.
Genuinely curious.
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.
Big companies had to roll out their own authentication layer on top. When OIDC came into play, it was already too late.
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.
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
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...
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.
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.
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