Side Notes

never finished…

Spring OAuth 2

This post is a quick analysis of the Spring implementation of OAuth 2.0 code flow (RFC6749 Section 4.1) with the minumum application code. The setup consists of the authorization server, the resource server, and the client.

The flow starts when a user hits localhost:9999/client/resource

1
1
2
GET /client/resource HTTP/1.1
Host: localhost:9999

Client endpoints are protected by Spring Security, therefore it redirects the request to login endpoint

2
1
2
3
4
5
6
7
8
9
10
HTTP/1.1 302
Set-Cookie: JSESSIONID=67CB7BD60760EDD84BD0884FF9F09651;path=/client;HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Location: http://localhost:9999/client/login
Content-Length: 0

Browser tries to load login page

3
1
2
3
GET /client/login HTTP/1.1
Host: localhost:9999
Cookie: JSESSIONID=67CB7BD60760EDD84BD0884FF9F09651

Client delegates the authorization to OAuth server

4
1
2
3
4
5
6
7
8
9
HTTP/1.1 302
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Location: http://localhost:8080/oauth/authorize?client_id=my-trusted-client&redirect_uri=http://localhost:9999/client/login&response_type=code&state=JUkFF6
Content-Length: 0

OAuth flow technically starts here. state is randomly generated by Spring. client_id is configured in Client’s application properties.

5
1
2
GET /oauth/authorize?client_id=my-trusted-client&redirect_uri=http://localhost:9999/client/login&response_type=code&state=JUkFF6 HTTP/1.1
Host: localhost:8080

Since User is not authenticated yet Spring shows basic dialog asking for user name and password.

6
1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 401
WWW-Authenticate: Basic realm="Spring"
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
Content-Type: text/html;charset=UTF-8
Content-Language: en-US
Content-Length: 344

User types dave:secret. These are credentials stored in Server database.

Authorization header here contains User credentials.

7
1
2
3
GET /oauth/authorize?client_id=my-trusted-client&redirect_uri=http://localhost:9999/client/login&response_type=code&state=JUkFF6 HTTP/1.1
Host: localhost:8080
Authorization: Basic ZGF2ZTpzZWNyZXQ=

Server verifies User credentials and creates session.

8
1
2
3
4
5
6
7
8
9
10
HTTP/1.1 200
Set-Cookie: JSESSIONID=0517E6F4E263721C8E75A1378D51179F;path=/;HttpOnly
Cache-Control: no-store
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
Content-Type: text/html;charset=UTF-8
Content-Language: en-US
Content-Length: 573

The authorization page gives User a choice to authorize or deny Client.

User clicks Authorize button.

9
1
2
3
4
5
6
7
8
9
10
11
POST /oauth/authorize HTTP/1.1
Host: localhost:8080
Content-Length: 44
Authorization: Basic ZGF2ZTpzZWNyZXQ=
Origin: http://localhost:8080
Content-Type: application/x-www-form-urlencoded
Referer: http://localhost:8080/oauth/authorize?client_id=my-trusted-client&redirect_uri=http://localhost:9999/client/login&response_type=code&state=JUkFF6
Cookie: JSESSIONID=0517E6F4E263721C8E75A1378D51179F

Form item: "user_oauth_approval" = "true"
Form item: "authorize" = "Authorize"

Server creates a new session, generates a random code, and redirects the response to redirect_uri supplied on step 7.

10
1
2
3
4
5
6
7
8
9
10
HTTP/1.1 302
Set-Cookie: JSESSIONID=6503F28F4068C83EC7BE0F5EC163D06F;path=/;HttpOnly
Cache-Control: no-store
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
Location: http://localhost:9999/client/login?code=WEJz0f&state=JUkFF6
Content-Language: en-US
Content-Length: 0

Browser, with Client and Server sessions saved in cookies, gets the redirected Client URI.

11
1
2
3
4
GET /client/login?code=WEJz0f&state=JUkFF6 HTTP/1.1
Host: localhost:9999
Referer: http://localhost:8080/oauth/authorize?client_id=my-trusted-client&redirect_uri=http://localhost:9999/client/login&response_type=code&state=JUkFF6
Cookie: JSESSIONID=67CB7BD60760EDD84BD0884FF9F09651; JSESSIONID=6503F28F4068C83EC7BE0F5EC163D06F

Spring OAuth Client requests access token implementing RFC6749 Section 4.1.3. Client authenticates itself through Authorization header passing credentials configured in Client application properties.

12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /oauth/token HTTP/1.1
Authorization: Basic bXktdHJ1c3RlZC1jbGllbnQ6bXktdHJ1c3RlZC1jbGllbnQtcGFzcw==
Accept: application/json, application/x-www-form-urlencoded
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/1.8.0_112
Host: localhost:8080
Connection: keep-alive
Content-Length: 101

Form item: "grant_type" = "authorization_code"
Form item: "code" = "WEJz0f"
Form item: "redirect_uri" = "http://localhost:9999/client/login"

Server verifies Client credentials stored in the database and exchanges authorization code for access token implementing RFC6749 Section 4.1.4. scope is populated from the corresponding database column.

13
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
HTTP/1.1 200
Cache-Control: no-store
Pragma: no-cache
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8

{
  access_token: 16d91f9e-69c5-41a9-bd0f-df8308589784,
  token_type: bearer,
  refresh_token: 8a283a19-7524-43d7-95bd-bc3a76260f3d,
  expires_in: 59,
  scope: read write trust
}

Although it’s not a part of RFC6749, Client sends access token verification request to Server. Once again Client authenticates itself through Authorization header.

14
1
2
3
4
5
6
7
8
POST /oauth/check_token HTTP/1.1
Accept: application/json, application/*+json
Authorization: Basic bXktdHJ1c3RlZC1jbGllbnQ6bXktdHJ1c3RlZC1jbGllbnQtcGFzcw==
Content-Type: application/x-www-form-urlencoded
User-Agent: Java/1.8.0_112
Host: localhost:8080

Form item: "token" = "16d91f9e-69c5-41a9-bd0f-df8308589784"

Server verifies Client credentials and access token and responds with User/Client metadata.

15
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8

{
  exp: 1481467593
  user_name: dave
  authorities: [ROLE_USER]
  client_id: my-trusted-client
  scope: [read, write, trust]
}

Client redirects User browser to the URL requested initially on step 1. It also resets the session.

16
1
2
3
4
5
6
7
8
9
HTTP/1.1 302
Set-Cookie: JSESSIONID=460BEFFA884E6349212F8D2EA50BF776;path=/client;HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Location: http://localhost:9999/client/resource

This step is the same as step 1 but with valid Client and Server sessions.

17
1
2
3
4
GET /client/resource HTTP/1.1
Host: localhost:9999
Referer: http://localhost:8080/oauth/authorize?client_id=my-trusted-client&redirect_uri=http://localhost:9999/client/login&response_type=code&state=JUkFF6
Cookie: JSESSIONID=460BEFFA884E6349212F8D2EA50BF776; JSESSIONID=6503F28F4068C83EC7BE0F5EC163D06F

As a part of business logic Client sends a request to Resource server suppling bearer token as required by RFC6750 Section 2.

18
1
2
3
4
5
GET /me HTTP/1.1
Authorization: bearer 16d91f9e-69c5-41a9-bd0f-df8308589784
Accept: text/plain, application/json, application/*+json, */*
User-Agent: Java/1.8.0_112
Host: localhost:8888

Resource server calls OAuth Server to verify the access token implementing RFC7662 Section 2.1. Resource authenticates itself through Authorization header suppling credentials configured in its application properties.

19
1
2
3
4
5
6
7
8
POST /oauth/check_token HTTP/1.1
Accept: application/json, application/*+json
Authorization: Basic bXktcmVzb3VyY2U6bXktcmVzb3VyY2UtcGFzcw==
Content-Type: application/x-www-form-urlencoded
User-Agent: Java/1.8.0_112
Host: localhost:8080

Form item: "token" = "16d91f9e-69c5-41a9-bd0f-df8308589784"

Server verifies Resource credentials and access token and responds with User/Client metadata.

20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8

{
  exp: 1481467593
  user_name: dave
  authorities: [ROLE_USER]
  client_id: my-trusted-client
  scope: [read, write, trust]
}

Since the access token is valid Resource server replies to Client

21
1
2
3
4
5
6
7
8
9
10
11
HTTP/1.1 200
Set-Cookie: JSESSIONID=4D7F3BE1EFD37A99D9878A0037A0F8A5;path=/;HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8

{name:dave}

Client returns the response to User

22
1
2
3
4
5
6
7
8
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: text/html;charset=UTF-8

Specifications

  • RFC6749 The OAuth 2.0 Authorization Framework
  • RFC6750 The OAuth 2.0 Authorization Framework: Bearer Token Usage
  • RFC6819 OAuth 2.0 Threat Model and Security Considerations
  • RFC7009 OAuth 2.0 Token Revocation
  • RFC7591 OAuth 2.0 Dynamic Client Registration Protocol
  • RFC7636 PKCE by OAuth Public Clients
  • RFC7662 OAuth 2.0 Token Introspection