Skip to content
Chapter 40 · Spring Boot

Spring Security Part 7: OAuth 2.0 Explained ​

In part 6 our own application issued the tokens.

OAuth (Open Authorization) is an authorization framework: it lets a third-party application access a user's protected data held somewhere else, securely and with the user's permission.

The familiar example: on some website you click "Sign in with Google". You are telling Google: "let this third-party site use some of my data (name, email)". Google shares just that, and the site welcomes you by name without ever seeing your Google password. (Using OAuth to log in is strictly done with OpenID Connect, a layer on top of OAuth; see the next part.)

The four roles ​

RoleWho, in the example
Resource ownerYou, the user who owns the data
ClientThe third-party app that wants the data (for example, Instagram offering "sign in with Gmail")
Authorization serverGoogle's server that authenticates you, asks for your consent and issues tokens
Resource serverGoogle's server that hosts your data (profile, email) and serves it to holders of a valid token

The authorization server and the resource server can be one service, but they are usually separate components.

Grant types ​

A grant type is a mechanism for the client to obtain an access token:

  1. Authorization code: the important, widely used one.
  2. Implicit
  3. Resource owner password credentials
  4. Client credentials
  5. Refresh token

The authorization code grant ​

The case: you (resource owner) want to log in to Instagram (client) using your Gmail account; Google holds your data.

The endpoint names below are illustrative; each provider documents its own, and the RFCs (RFC 6749 for OAuth 2.0) are the reference.

0. Client registration ​

Before showing "Sign in with Gmail", the client registers with the authorization server (usually in the provider's developer console):

http
POST /register
{
  "client_name": "My App",
  "redirect_uris": ["https://myapp.com/callback"]
}
json
{
  "client_id": "abc123",
  "client_secret": "secretXYZ"
}

The client secret is known only to the client and the authorization server; it proves the client's identity later. The redirect URIs are where the authorization server may send the user back.

1–4. Getting an authorization code ​

You click "Sign in with Gmail", and the client redirects your browser to the authorization server:

http
GET /authorize?response_type=code
              &client_id=abc123
              &redirect_uri=https://myapp.com/callback
              &scope=email profile
              &state=xyz123
  • response_type=code: the client expects an authorization code back.
  • client_id: which client is asking.
  • redirect_uri: where to send you back. Optional; if given, it must match one registered; if not, a registered one is used.
  • scope: space-separated list of what the client wants to access (email, profile, ...).
  • state: a random, hard-to-guess value that protects against CSRF (below).

You log in to Google if needed, and Google shows the consent screen: "My App wants to see your email and profile. Allow?". You allow, and Google redirects you back to the client:

302 Location: https://myapp.com/callback?code=AUTH_CODE_123&state=xyz123

Why state? ​

Without it, an attacker can start their own login, obtain an authorization code for their account, and not use it. Then, while you are logging in, they get the client to accept their code as the answer to your request. The client exchanges it, and you end up signed in to the client as the attacker. Anything you then upload (to "your" drive, say) goes to the attacker's account. That is a CSRF attack.

With state, the client sends a unique random value with each /authorize request and checks that the same value comes back with the code. A response with a missing or different state is rejected. An authorization code can also be used only once.

5–6. Exchanging the code for tokens ​

The client calls the token endpoint directly (server to server), this time proving its identity with the secret:

http
POST /token
grant_type=authorization_code
&code=AUTH_CODE_123
&redirect_uri=https://myapp.com/callback
&client_id=abc123
&client_secret=secretXYZ
json
{
  "access_token": "ACCESS_TOKEN_ABC",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "REFRESH_TOKEN_XYZ"
}
  • access_token: short-lived (here 3600 seconds, one hour). It can be a JWT or an opaque string that only the authorization server can interpret.
  • token_type: Bearer: send it as Authorization: Bearer <token>.
  • refresh_token: long-lived; used to get new access tokens.

7–8. Using the access token ​

http
GET /userinfo
Authorization: Bearer ACCESS_TOKEN_ABC

The resource server checks the token: for a JWT it can verify the signature and expiry itself; for an opaque token it asks the authorization server (token introspection). If the token is valid and covers the requested scope, it returns the data; otherwise 401. With your email and profile, the client signs you in.

Refreshing ​

When the access token expires:

http
POST /token
grant_type=refresh_token
&refresh_token=REFRESH_TOKEN_XYZ
&client_id=abc123
&client_secret=secretXYZ
json
{
  "access_token": "NEW_ACCESS_TOKEN",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "NEW_REFRESH_TOKEN"
}

No username, password or code is needed.

The other grant types ​

One step instead of two: /authorize with response_type=token returns the access token directly in the redirect:

https://myapp.com/callback#access_token=ACCESS_TOKEN_ABC&token_type=Bearer&expires_in=3600&state=xyz123

There is no code exchange and no refresh token. Because the token travels through the browser, current best practice (the OAuth 2.0 Security Best Current Practice, and OAuth 2.1) deprecates it. Browser and mobile apps use the authorization code grant with PKCE instead, which also protects public clients that cannot keep a secret.

There is no /authorize step: the user gives their username and password to the client, which sends them to the token endpoint:

http
POST /token
grant_type=password
&username=john@gmail.com
&password=USER_PASSWORD
&client_id=abc123
&client_secret=secretXYZ
&scope=email profile

The response includes an access token and a refresh token; refreshing no longer needs the password. Handing your password to a third party defeats the purpose of OAuth, so this grant is deprecated too; it survives only for highly trusted first-party clients.

Client credentials ​

For when the client is itself the resource owner: machine-to-machine calls, such as one backend service calling another with its own identity, no user involved.

http
POST /token
grant_type=client_credentials
&client_id=abc123
&client_secret=secretXYZ
&scope=read

The response has an access token and no refresh token: when it expires, the client simply asks again with its id and secret.

Next: Part 8 implements OAuth2 login in Spring Boot, with GitLab and Auth0 as the login providers.

Check yourself ​

  1. What is OAuth, in one sentence?
  2. Name the four roles and who plays each in "Sign in with Gmail on Instagram".
  3. What does the client get at registration, and who knows the client secret?
  4. What parameters does the /authorize request carry, and what does each mean?
  5. What does the client receive after you give consent, and where?
  6. Describe the attack that state prevents.
  7. Why is the code exchanged at the token endpoint instead of the access token being returned directly?
  8. How does the resource server validate a JWT access token? An opaque one?
  9. How does a client get a new access token without the user?
  10. Why are the implicit and password grants discouraged, and what replaces them?
  11. When is the client credentials grant used, and why is there no refresh token?
Answers
  1. An authorization framework that lets a third-party app access a user's protected data with the user's permission.
  2. Resource owner: you. Client: Instagram. Authorization server: Google's auth server. Resource server: Google's server with your data.
  3. A client id and client secret; only the client and the authorization server.
  4. response_type=code (expect a code), client_id, redirect_uri (where to return), scope (what access), state (anti-CSRF value).
  5. An authorization code (and the same state), as parameters on the redirect URI.
  6. An attacker gets the client to accept the attacker's authorization code in your login, so you end up signed in as the attacker; state ties each response to the request the client actually made.
  7. The exchange happens server to server and requires the client secret, so the token never passes through the browser and only the real client can obtain it.
  8. JWT: check the signature and expiry itself. Opaque: ask the authorization server (introspection).
  9. With the refresh token, grant_type=refresh_token.
  10. Implicit exposes the token in the browser; password hands the user's password to the client. Authorization code with PKCE.
  11. Machine-to-machine calls where the client acts for itself; it can always get a new token with its own credentials.