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.
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.
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).
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.
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
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).
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.
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.
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.
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:
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)
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.
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.