An illustrated guide to OAuth

258 egonschiele 54 8/25/2025, 12:29:48 PM ducktyped.org ↗

Comments (54)

gethly · 8h ago
I am implementing oauth right now, along with oidc. I must say that for such a simple concept, getting to the facts that help me to actually implement it is insanely hard. I have no idea why but everywhere i look it just seems like it only scratches the surface and you get no tangible information that you can use to actually implement it in code. I ended up mostly browsing the specs and grok was insanely helpful to explain meaning of various things where information was lacking or buried deep in documentation/specifications. I would say this was the first time where i actually appreciated these new "AIs", which i don't use at all.
caseysoftware · 2h ago
Yes, 100% agreed.

I launched and worked on OAuth 2.0 at Okta for ~5 years and spent most of my time showing people how to do it well and (gently) finding the holes and mistakes in their implementations. Sure, we were selling "OAuth as a Service" but most had introduced usability problems (at minimum) and gaping security vulns (at worst).

For a deep dive, check out Aaron Parecki's book: https://oauth2simplified.com/ - he's deeply involved in the (coming) OAuth 2.1

When I led re-implementation at pangea.cloud over the last couple years, we dropped most of the capabilies deprecated in 2.1 (resource owner password, implicit) and went straight to Auth Code with PKCE to make it a bit more manageable.

I walk through that progression/simplication here: https://speakerdeck.com/caseysoftware/the-many-layers-of-oau...

gethly · 28m ago
What is your opinion on token response type and/or id_token for oidc being part of the fragment of redirect uri? I have noticed that apple only supports "code" response type, which is the most secure way. Downside is that it requires a back channel and a second request to be made, but i cannot imagine a use case where this would be a problem as I don't see a pure Single-Page Application having any use for this in a any way, except purely rendering some protected data in a different format, which seems like a silly use case.
interroboink · 7m ago
In case it helps you, I found this overview helpful: https://metacpan.org/dist/LWP-Authen-OAuth2/view/lib/LWP/Aut...

Clearly written by someone who was also frustrated by the experience (:

jwr · 7h ago
When I implemented Oauth2 (Authorization Code Grant, both sides), I found this guide to be quite helpful: https://alexbilbie.github.io/guide-to-oauth-2-grants/

One thing I found after a while: even though the refresh tokens should theoretically not expire, many sites do expire them. You have to refresh every once in a while to maintain a usable refresh token.

Many people will tell you to "just use a library", but I found that the contact surface of oauth with your app is quite large, such that a library might not actually help much. This (among other reasons) is why I wrote my own implementation (Clojure).

pverheggen · 5h ago
FWIW, this is fairly out of date - password grant must not be used and authorization code should be used in place of implicit. I highly recommend anyone dealing with OAuth to read the BCP and not just the spec, especially if you're rolling your own:

https://datatracker.ietf.org/doc/html/rfc9700

As for your API surface, typically you'd handle this at the gateway level, then individual services don't have to perform authorization.

maxwellg · 4h ago
I would also recommend the OAuth 2.1 IETF draft as a precursor to the BCP: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-...

Although it isn't a published RFC yet, it intends to replace several sometimes-conflicting previous RFCs + the BCP with a single document.

gethly · 6h ago
> the refresh tokens should theoretically not expire

That is subjective. In essence, they should last long enough so the client can use them to get new access token without the user(resource owner) having to authorise a new grant. Each client is different with different needs and the scopes might be too sensitive to provide a long lasting access. So as usual, it depends.

In my server implementation, access tokens are valid for one hour and refresh tokens for 30 days. I also return refresh tokens with each access token request, so as long as the client makes at least one request per month, they do not have to bother the user for a new grant.

I just wish the spec would have a dedicated "refresh_expires_in" field in addition to "expires_in" for refresh tokens, so the client would be better informed about this. As refresh tokens are part of the spec, though optional, their life span information is lacking here.

Pet_Ant · 3h ago
How hard is it to get a suggestion like that in? I mean it seems fool proof, make it optional and there should be no problem.
jwr · 6h ago
Yes, I have no problem with refresh tokens expiring, but I wish the RFC did explicitly include this functionality along with a "refresh_expires_in" field, as you wrote.
arwhatever · 59m ago
Fantastic comment from an earlier posting https://news.ycombinator.com/item?id=35720336

“… one of the principle issues is that it's less a protocol and more a skeleton of a protocol.”

aurecchia · 7h ago
Are you implementing an auth server or integrating with one?

Regardless, the last time I dug into this topic I ended up feeling the same. The web is littered with articles that scratch the surface and only cover the basics. They often leave out the details, which IME ended up making things more difficult to understand. What was the most helpful, as you said, was to follow the RFCs and the OIDC spec directly.

What might also be useful, if you are implementing an auth server, is to look at existing implementations. Duende IdentityServer (https://github.com/DuendeSoftware/products/tree/main/identit...) is the most widely-used one in the .NET space.

olavgg · 7h ago
Before I knew about Keycloak, I need to figure out how to use Spring Boot to authenticate via Azure Entra Id. I could't use Spring Boot Security OAuth2 as I couldn't figure out how to bind Entra ID groups to roles in Spring Boot. I saw a great video from Okta where they broke down all details down to each http request (don't remember the link to the video), and then implement each http request/redirects to Entra ID. Finally I got the token and could then use the Graph API to get group memberships for binding a Spring Boot role.

I still used Spring Sessions though, where a successfull authed user got a new Spring Session. The reason was that I liked the idea of having beans with session scope, for example where each user/role has access to a specific database schema.

peterldowns · 7h ago
Ancient, but potentially also helpful due to documentation and tests, is my old django implementation: https://github.com/peterldowns/djoauth2 . I’m sure it doesn’t run out of the box anymore due to Django changes but maybe another good reference server.
commandlinefan · 7h ago
> Are you implementing an auth server or integrating with one?

And OAuth has somehow managed to be _harder_ to integrate with an existing implementation of than just to implement from scratch.

gethly · 7h ago
I am implementing oauth server with open id provider capabilities. I agree with what you sad, that is my experience as well.
mettamage · 6h ago
So how are you guys finding this illustrated guide, is it any good?
9dev · 2h ago
A while ago, I set out to understand OAuth properly and built a fully compliant authorisation server on SvelteKit, following all relevant RFCs, simply by… reading them all.

When you get used to the technical writing, it’s actually pretty straightforward—most of them actually document the endpoint structure and payloads, error codes, and so on. After that, the most complicated part is organizing your code to be modular and handle persistence right.

I can really recommend doing this once, and once the pieces start to fall into place, you’ll be able to understand most OAuth issues you’ll ever come across!

notatoad · 1h ago
i did the same last month - i used chatgpt heavily to explain oauth to me. and then confirmed what it was telling me my checking the actual spec documents.

i think, as the article says, oauth is so varied that while there are documents, none of them are tailored enough for your specific use case. but an LLM can narrow it down to exactly your use case, which is what you really need to implement it.

EthanHeilman · 6h ago
Yep, I had to get deep into OIDC for OpenPubkey and it basically involved me having to build teaching materials and notes for myself. I had a bunch of Google docs slides I consult every time I got confused about what something did. A major motivation for writing the OpenPubkey paper was to have detailed notes on how OIDC works to remind myself.

I recommend sections I and II of the OpenPubkey paper to anyone trying to understand OIDC public clients. I consult it at least once a month: https://eprint.iacr.org/2023/296

OkayPhysicist · 3h ago
This page :

https://infosec.mozilla.org/guidelines/iam/openid_connect.ht...

Was by far the most useful information about OIDC I could find when I was implementing an integration.

manojlds · 3h ago
I haven't seen the OP yet...are you saying the OP is not worth it and recommending these other links?
OkayPhysicist · 3h ago
OP is largely conceptual. You won't be able to read OP and go write an integration yourself. My link contains all the information necessary to actually implement an OIDC integration.
pwlb · 5h ago
This is due to many parts of the system being spread across multiple IETF RFCs, which happens as OAuth was improved and made more secure over time. Efforts are underway by combining all important parts into OAuth 2.1, otherwise have a look at FAPI 2.0 security profile for high assuance use cases.
fmbb · 6h ago
This is because OAuth is just SAML with JSON designed by committee so it has all the bells and all the whistles and everything is optional and depends on who you integrate with and how.
tptacek · 2h ago
Point of order: first, OIDC is SAML, not OAuth (OAuth by itself solves a different problem) and second, OIDC is much better than SAML --- the committee did its job there.
chankstein38 · 3h ago
I also don't understand the reason but this is my experience on 80% of the internet basically. Articles that purport to share how to do something then spend most of the article talking about stuff I don't care about, then we finally get to the complicated part then they skip some detail or use some library that I don't want to use and then they're just like "bam it's done! woo"
znpy · 4h ago
oauth is one of those things i've studied, re-studied, implemented and re-implemented multiple times in my work life and i always end up forgetting it.

at this time I keep a copy of rfc6749 binded and highlighted near my desk... every now and then i have to go look at some detail.

also, somehow the openid spec is a bunch of documents that aren't really formatted for being printed. it really feels like the authors are implicitly assuming no one is going to actually read them.

TofuLover · 6h ago
I don't think the part about front and back channels is quite correct. GET and POST requests are both encrypted in HTTPS -- including the URL (but not the domain, as DNS resolution happens separately). Front and back channel are more to do with trust boundaries, and what information is public vs private from the client's perspective.
mrmuagi · 3h ago
The urls are logged usually and also like the other commentator pointed out can be stored in browser history/bookmarked.

I've seen just a general recommendation to avoid urlencoding parameters -- I guess that's why?

aszen · 3h ago
Main point is that the url is store in browser history and is never private.
cathalc · 6h ago
Yeah this made zero sense to me - I have never seen someone consider POST secure because it can't "be seen".

Security through obscurity and all that...

doublerabbit · 2h ago
Is there anyway to secure a POST request at the backend, without client side encryption?

The server processing the POST is still receiving the information posted regardless if the client is HTTPS or not.

Say, you're attempting login, the password is still received by the server and which you do with whatever when processing.

What's not stopping someone from injecting a trace on that receiving function?

In other-words, How would you secure the server processing the POST request information?

BrandoElFollito · 2h ago
You can't. It is just a matter of reducing the risk surface. With a GET someone may add parameters, with a POST they would send the data in the post (which is often the main point of a POST).

Since all typical web servers/processors only loh the call and not the body there is a lesser probability of a leak.

I am writing this as someone who manages cybersecurity and is offering faced with not enough information in investigations because of that. This is also the reason that I used "typical" and "usually" above - it is pretty weird what people send and how they process what they receive.

tbarbugli · 26m ago
> anyone can see what URLs you are visiting

this is not correct with HTTPS (query params are not part of the plain text)

chasil · 47m ago
iamcreasy · 1h ago
Very clearly written article.

Please write follow ups exploring other OAuth flows.

Pet_Ant · 1h ago
This could have used a crudely animated .gif to pull it all together. Or even a slide show or something.
languagehacker · 7h ago
This is a nice one! Reminds me of the [beer garden analogy](https://www.stevetecharc.com/blog-posts/understanding-oauth-...) which I also thought was a good entree into OAuth.
homakov · 6h ago
IMO OAuth2 is very poorly designed. It has several structural issues: "Connect this OAuth provider" hijack your main account, redirect hijack allows to leak either auth codes through Referrer or access_token through #hash passing, "state" CSRF token is optional and usually ignored etc

I have an old writeup on that and solution to it https://sakurity.com/oauth - better analyze it with LLM if interested in authorization protocols

ted_dunning · 4h ago
Your comments are so highly abbreviated as to be nearly impossible to understand. I suspect that unintelligibility is leading to it being heavily downvoted.

The addition of the comment about LLMs isn't really helping.

homakov · 4h ago
I wasn’t criticizing the guide — just pointing out real OAuth2 pitfalls that still affect users.

The spec itself made mistakes:

• Silent account hijack via “Connect this provider.”

• Redirect leaks of code (via Referrer) or access_token (via #hash).

• CSRF because state was optional and often ignored.

The point is: these aren’t obscure edge cases, they’re structural issues baked into the protocol.

tiffanyh · 7h ago
> The example I'm going to use is YNAB. If you haven't used it, YNAB is like a paid version of Mint.

I'm glad this analogy was used ... because I too feel like OAuth and Open Banking is effectively the same thing.

Am I alone thinking that and/or why is it actually two different things?

chrisgd · 5h ago
Love the illustrations! Great descriptions, thank you
losvedir · 2h ago
This is a pretty good guide! I didn't get any AI slop vibes from it, so I'm assuming it was handwritten, which I appreciate.

I think I would suggest that PKCE is not really "less secure" than a client secret. It serves somewhat of a different purpose, and is actually frequently recommended even with a client secret. Its main purpose is "flow integrity" and ensuring that the same client is involved all the way through the redirects.

I think it also didn't really put the authorization code grant in context with the other possibilities. This really covered the "3 legged redirect" flow pretty well, which is what most people associate with OAuth. But the OAuth2 framework has a bunch of different grants you can use for different purposes. The Client Credentials one is pretty common, for server-to-server use cases, as well as fancier versions of it like the JWT Bearer flow.

Finally, taking an advantage of general OAuth2 discussion, since I've been noodling on it myself from the point of view of creating an "app ecosystem": since the redirect_uri is such an integral part of the security of it, and recommendations are for exact matches now rather than just prefixes and wildcards and such, how do folks handle OAuth2 when the app isn't owned by a single entity, but rather something like ServiceNow or Backstage which is self-hosted?

That is, you want your resource server to behave like "this was a request from a customer's ServiceNow instance", and all such requests are in some sense related. However, they're not really the same client, because you can't manage a client secret across all the installations. It's somewhat like a mobile app, which also can't manage a client secret, but that at least can share the same underlying OAuth Client because it can register a single, unique redirect URI.

I have other questions about things like how to fit the client credentials grant into a multi-tenant system... if these are things you've worked on, I'd love to hear from you! My email should be on my profile here.

fcpguru · 8h ago
where is the "session fixation" / token hijacking attack graphic? The history of 1.0 and the rush to put out OAuth 1.0a I will always remember. The year was 2008 and us yammer engineers implemented this new best practice auth system. It went live. And then suddenly a few days later someone in the office proved how the hijack was possible.
7bit · 6h ago
Why is that relevant. We are at OAuth 2.0. who cares about what's been 17 years ago?
brabel · 5h ago
2.1 is just around the corner.
ted_dunning · 4h ago
And 2008 is still 17 years ago.
brabel · 2h ago
What??
politelemon · 7h ago
This is well written and helped me understand quite a bit. I think a pkce edition would be appreciated considering how prevalent and recommended it is.
mdaniel · 6h ago
Depending on if you're shopping for the server or the client side of the implementation, I actually found the RFC's example section was actually helpful https://datatracker.ietf.org/doc/html/rfc7636#appendix-B The only thing that jammed me up, even while preparing this comment, is that they named both parameters very similar to one another which is :-(

One can think of the whole exchange as a way for the client to prove future knowledge, but only actually transmitting the held back "secret" during token redemption. Let's use just a random integer

1. r = randomInt()

2. future = base64(sha256(r))

3. /authorize?client_id=cid-123&code_challenge_method=S256&code_challenge=${future}

The server has to retain the ${future} in durable storage, because it's going to need access to it, plus the ${code} that it's about to return, in step 5

4. <-- &code=abc789

5. /token?code=abc789&code_verifier=${r}

Now, the server can repeat that same sha256 dance from step 2 and establish that the client presenting "r" means it really was the same requester in step 3, because no one else, even with that same IP address and same access to the client_id sniffed in transit, could have known "r" for sufficiently random values of "r", thus proving their key during code exchange

calexanderaz · 56m ago
Here’s a description of all the major flows of OAuth 2.0, along with a visual description of PKCE (and the attack it prevents)

https://youtu.be/tpIXmmV4ib4

ttb-2134 · 6h ago
This is one of the best OAuth explainers out there. Amazing