Spring Security & OAuth 2.0 - In-Depth - header image

Spring Security & OAuth 2.0 - In-Depth

Last updated on March 25, 2021 -

(Buy now if you're already convinced!)

You can use this guide to get a deep understanding of OAuth 2.0 and how you integrate Spring Security with it.

(Editor’s note: At ~7000 words, you probably don’t want to try reading this on a mobile device. Bookmark it and come back later.)

Introduction

The confusing OAuth 2.0 world

It seems like almost every company out there is using OAuth (2.0), but understanding what it does or how it works leads to a lot of head-scratching.

You get bombarded with words like Authorization Server, Protected Resource, Client, Access Tokens, Refresh Tokens, different Authorization Flows, grant types, JWT, JOSE, OpenID Connect, social logins and the list seemingly never stops.

Then add Spring Security on top of that, a framework that is already complex enough without OAuth, consisting of different projects, modules and versions supporting different parts of the OAuth universe.

This could easily lead to learned helplessness, so let’s try and bring some light into the OAuth darkness.

What is OAuth 2.0

The short answer:

At its core, OAuth 2.0 is just an authorization framework, granting clients access to protected resources via an authorization server.

Wow, that sentence doesn’t help at all, does it?

Luckily, there’s also a long answer:

The remainder of this document. Note, that whenever I mention OAuth in this article, I’m referring to 2 of the OAuth protocol, i.e. OAuth 2.0.

Changelog

Update 2 - March 25, 2021

  • Added: Single Page Javascript & Native Apps

  • Added: How does the PKCE protocol work?

  • Added: The SPA & Native App Callback Problem

  • Added: FAQ - Can a resource server simultaneously be a client?

Update 1 - Sep 29, 2020

  • Significantly reworked social login section (Facebook, GitHub), with explanations and code examples

Initial Release - Aug 21st, 2020

  • First Release

  • Containing: Chapters on Spring Security & Authorization Servers, Clients and Resource Servers

Beta 1 — Aug 13th, 2020

  • Initial Beta

  • Containing: OAuth Overview, Flows, (parts on) JWT and JOSE.

OAuth 2.0 - Fundamentals

As always, it helps to start with the basics. It absolutely does not make sense to jump right into Spring Security’s OAuth integration, before having a firm grasp of the OAuth 2.0 basics.

Forget all the social logins (like 'login with GitHub') or whatever you might associate with OAuth, for now, and focus on what the original problem is that it tries to solve.

Imagine you have a bank account (or in this website’s case a Stripe account). Also, imagine, you want to give a 3rd party (limited) access to that bank account.

That 3rd party could be a consumer app which tries to categorize and report on your monthly spendings or, in my case, a web-service that generates PDF invoices for all transactions in my Stripe account.

The problem:

  • You don’t really want to give that 3rd party application your username/password combination to your account.

  • Also, you want to somehow limit what the 3rd party application can do with your account: I.e. allow it to read your banking transactions, but certainly not allow it to create new transactions.

  • You also want to be easily able to revoke that access, i.e. not let the 3rd party access your bank account anymore, whenever you say so. (If they have your credentials, good luck with that.)

In other words, the original problem that OAuth tries to solve in this very specific example:

How can you safely give a 3rd party application limited and revokable access to your bank account?

Meet: Resources, Owners & Clients

To answer that question, let’s get technical and learn about OAuth’s terminology.

Rephrasing the problem above in OAuth speak looks like this:

How can the resource owner (you) give a client (3rd party software) scoped access (read or write or both) to a protected resource (bank account)?

Let’s break this down.

  • The resource owner is you, the user, who holds the rights to something.

  • That something is called protected resource, i.e. your bank account.

  • The 3rd party application, who wants to access the protected resource in your place, is called the client. Never confuse the client with the resource owner.

  • (Additionally, the client only gets scoped access, i.e. it can only read transactions in this example)

These are three main players of every OAuth interaction, but a fourth one is missing. Who is it?

Meet: Authorization Servers

Let’s have a quick Q&A session.

Q: How does the client (3rd party software) convince the protected resource (bank account) that it is allowed to read the transactions in your bank account?

A: Simple: It needs to show up at the protected resource with a valid access token. For now, if it helps, think of an access token as a physical key card and your bank account as a vault with a printed list of transactions inside.

The client doesn’t care how the key card is built and how it works etc. The client only cares that he has it and it opens up the vault.

Q: I’ve heard that access tokens are sometimes also called bearer token. Why?

A: Because whoever bears (fancy word for: holds) a (valid) token, gets access to the protected resource. Think back to your key card. Anyone who bears the key card could open your bank account, with no more questions being asked.

Q: Ok ok. Now, where does the client get that access token from?

A: From another involved party, that everyone (you, the client, the protected resource) trusts: the authorization server. Its main job is to hand out those access tokens.

In our banking example, the protected resource (bank account) and authorization server would both be provided by the bank. It could be two different systems, or the same system with two different endpoints.

Q: But then any client can simply go to the authorization server and ask for an access token?

A: Not quite. There’s one piece of information missing. The client only gets an access token from the authorization server, when you, the resource owner, successfully authenticates (think: login) at the authorization server and allows the client to get that access token.

A bit too abstract? In summary: The main goal of the OAuth 2.0 flow is for the client to get a valid access token. How that’s done specifically, we’ll see next.

The Access Token OAuth 2.0 Dance

Meet the (quite elaborate) access token dance. (A big thank you goes out to Andreas Eisele, who drew the flow chart below).

Getting and using an access token happens in 3 phases and you can see those phases in the flow above depicted by the horizontal, dotted lines.

Let’s break the phases down.

Phase 1 : Getting An Authorization Code

Imagine, you (the resource owner), are logged into the website of that 3rd party application (client) which categorizes your monthly spendings (household, electronics, utilities etc.). Let’s call that 3rd party application MoneyMoneyMoney for now.

There’s a big button:

Now what happens when you click that button?

The answer is, you get redirected away from that website, to an authentication endpoint of your bank.

And, upon successful login, the bank asks you if you want to grant MoneyMoneyMoney access to your bank account. It also displays you a list of "rights" that MoneyMoneyMoney is supposed to get.

In this case, it should only be able to access and manage transactions (like tagging them), not create new transactions.

If you click Authorize Access, what actually happens is that the authorization server generates a so-called authorization code and redirects you back to MoneyMoneyMoney’s website with that authorization code as a URL parameter.

This could result in the following request back to the client.

https://moneymoneymoney.com/callback?code=89sHBN123nd&state=...

With the authorization code being 89sHBN123nd (and ignoring the other parameters for now).

Now, your job, as the resource owner (user) is done. The client needs to turn that authorization code into an access token, for now.

Phase 2 : Getting An Access Token

This phase is essential, but happens entirely in the background. Hence, there are no fancy screenshots.

Upon receiving the authorization code, the client needs to turn it into an access token. This happens through a separate HTTP call to the authorization server, but there’s a tiny catch.

The client needs to present the authorization code and client credentials in that HTTP call.

Where do these client credentials come from?

At some point, before being able to do this whole OAuth dance, every client must register with an authorization server, so that the authorization server actually knows the client. In this process, the client gets its credentials.

So, if you are the developer of the MoneyMoneyMoney application, you would try to get a developer account at the bank and create a new OAuth application through its website.

This is the form that you’ll likely have to submit. The most important part is the callback URL, i.e. the URL from your application that the authorization server needs to call with the authorization code.

Upon successful registration, you will get a set of credentials. Which you can use to convert the authorization code into an access token.

(Note: More advanced readers might already know that, additionally, there is a process of dynamic client registration, but we will skip this for now and keep things static & simple).

The access token is returned as a JSON object and could look like this:

{
    "access_token": "lkjl5qwek516nbleearrgh",
    "token_type": "Bearer"
}

The interesting part is now this: Contrary to popular belief, there is no pre-defined format for <access tokens>, they can literally be any string, from "lkjl5qwek516nbleearrgh" to a Base64 encoded JSON Object (like the popular JWT - more on that in a bit). Some people might even use a wrongly indented Kubernetes YAML descriptor as access token ;).

Also: The client doesn’t care, nor know, nor understand what the access token it gets back is. It only needs to store it somewhere and then use it whenever it wants to get access to the bank account.

What others are saying

Share

Comments

let mut author = ?

I'm @MarcoBehler and I share everything I know about making awesome software through my guides, screencasts, talks and courses.

Follow me on Twitter to find out what I'm currently working on.