User Tools

Site Tools


security:sso-spa

This is an old revision of the document!


OIDC SSO Architecture

Overview

This system implements Single Sign-On (SSO) using:

  • OpenID Connect (OIDC)
  • OAuth2 Authorization Code Flow
  • PKCE (Proof Key for Code Exchange)
  • JWT Access Token
  • JWKS for signature verification

Components

Component Responsibility
SPA Browser frontend
ServerA Business API A
ServerB Business API B
ServerSSO Identity Provider (IdP)
JWKS Endpoint Public keys for JWT verification

Architecture

+-------------+
|    User     |
+------+------+ 
       |
       v
+-------------+
|     SPA     |
+------+------+ 
       |
       |
       v
+------------------+
|    ServerSSO     |
|  OIDC Provider   |
+--------+---------+
         |
         |
         v
+------------------+
| JWKS Endpoint    |
+------------------+

         ^
         |
         |
+--------+---------+
|     ServerA      |
+------------------+

+------------------+
|     ServerB      |
+------------------+

Endpoints

ServerSSO

Authorization Endpoint:

GET /oauth2/authorize

Token Endpoint:

POST /oauth2/token

UserInfo Endpoint:

GET /userinfo

JWKS Endpoint:

GET /.well-known/jwks.json

OIDC Discovery:

GET /.well-known/openid-configuration

JWT Example

Header

{
  "alg": "RS256",
  "kid": "key-2026"
}

Payload

{
  "iss": "https://sso.company.com",
  "sub": "123456",
  "aud": "spa-client",
  "exp": 1800000000,
  "iat": 1799990000,
  "scope": "openid profile email"
}

PKCE

SPA Generates

Code Verifier

X7sJ8M9nK2Q...

Code Challenge

BASE64URL(
 SHA256(code_verifier)
)

Example:

code_challenge=AbCdEf123

Scenario 1: User NOT Authenticated

Step 1 - User Opens SPA

https://app.company.com

SPA checks:

access_token exists ?

Result:

No

Step 2 - SPA Creates PKCE

Generate:

code_verifier
code_challenge
state
nonce

Store:

sessionStorage

Step 3 - Redirect to SSO

SPA redirects browser:

GET /oauth2/authorize
 ?response_type=code
 &client_id=spa-client
 &redirect_uri=https://app.company.com/callback
 &scope=openid profile email
 &state=abc123
 &nonce=xyz123
 &code_challenge=ABC
 &code_challenge_method=S256

Step 4 - User Not Logged In

ServerSSO checks session:

SSO Session Exists?

Result:

No

Show login page.

Step 5 - User Login

User enters:

username
password

ServerSSO validates credentials.

Step 6 - Create SSO Session

ServerSSO creates:

SSO Cookie

Example:

Set-Cookie:
SSO_SESSION=abc123
HttpOnly
Secure
SameSite=None

Step 7 - Authorization Code

ServerSSO generates:

authorization_code

Redirect:

https://app.company.com/callback
 ?code=AUTH_CODE
 &state=abc123

Step 8 - SPA Validates State

SPA verifies:

returned state == stored state

Step 9 - Exchange Code

SPA calls:

POST /oauth2/token

Request:

grant_type=authorization_code
code=AUTH_CODE
client_id=spa-client
code_verifier=X7sJ8M9nK2Q...
redirect_uri=https://app.company.com/callback

Step 10 - SSO Validates PKCE

ServerSSO:

SHA256(code_verifier)
==
stored code_challenge

Valid:

Issue Tokens

Step 11 - Token Response

{
  "access_token":"JWT",
  "id_token":"JWT",
  "expires_in":3600,
  "token_type":"Bearer"
}

Step 12 - SPA Calls ServerA

Request:

GET /api/orders

Authorization:
Bearer JWT

Step 13 - ServerA Validates JWT

ServerA reads:

kid

from JWT header.

Step 14 - ServerA Gets JWKS

If key not cached:

GET /.well-known/jwks.json

Response:

{
  "keys":[
    {
      "kid":"key-2026",
      "kty":"RSA",
      "alg":"RS256"
    }
  ]
}

Step 15 - ServerA Verifies

Verify:

  • Signature
  • exp
  • iss
  • aud

If valid:

200 OK

Step 16 - SPA Calls ServerB

Same JWT:

Authorization: Bearer JWT

Step 17 - ServerB Verifies JWT

Using same JWKS.

User accesses both systems without another login.

Scenario 2: User Already Authenticated

Initial Condition

Browser already contains:

SSO_SESSION cookie

from previous login.

Step 1 - User Opens SPA

SPA has no access token.

Step 2 - Redirect to SSO

SPA redirects to:

/oauth2/authorize

Step 3 - SSO Checks Session

ServerSSO finds:

SSO_SESSION exists

User already authenticated.

Step 4 - Skip Login Screen

No username/password required.

Step 5 - Generate Authorization Code

ServerSSO immediately creates:

AUTH_CODE

Step 6 - Redirect Back

https://app.company.com/callback
 ?code=AUTH_CODE
 &state=abc123

Step 7 - Exchange Code

SPA calls:

POST /oauth2/token

with PKCE.

Step 8 - Receive JWT

{
  "access_token":"JWT",
  "id_token":"JWT"
}

Step 9 - Access ServerA

Authorization: Bearer JWT

ServerA validates JWT via JWKS.

Step 10 - Access ServerB

Same token:

Authorization: Bearer JWT

ServerB validates JWT via JWKS.

Result:

No login page shown.
True Single Sign-On.

Sequence Diagram - Not Authenticated

User
 |
 | Open SPA
 v
SPA
 |
 | Redirect
 v
ServerSSO
 |
 | Login Page
 v
User
 |
 | Credentials
 v
ServerSSO
 |
 | Authorization Code
 v
SPA
 |
 | Token Request + PKCE
 v
ServerSSO
 |
 | JWT
 v
SPA
 |
 | Bearer Token
 +-----> ServerA
 |
 +-----> ServerB

Sequence Diagram - Already Authenticated

User
 |
 | Open SPA
 v
SPA
 |
 | Redirect
 v
ServerSSO
 |
 | Existing SSO Session
 |
 | Authorization Code
 v
SPA
 |
 | Exchange Code
 v
ServerSSO
 |
 | JWT
 v
SPA
 |
 +-----> ServerA
 |
 +-----> ServerB

Security Best Practices

  • Always use Authorization Code Flow + PKCE
  • Never use Implicit Flow
  • Use RS256 or ES256
  • Validate issuer (iss)
  • Validate audience (aud)
  • Validate expiration (exp)
  • Validate nonce
  • Validate state
  • Cache JWKS keys
  • Use HTTPS everywhere
  • Store tokens in memory when possible
  • Use short-lived access tokens
  • Rotate signing keys regularly

Summary

Browser Authentication:

  • SSO Session Cookie

API Authorization:

  • JWT Access Token

Token Validation:

  • JWKS

Login Flow:

  • Authorization Code + PKCE

Single Sign-On:

  • Managed by ServerSSO Session Cookie

Resource Servers:

  • ServerA
  • ServerB

Identity Provider:

  • ServerSSO

</code>

security/sso-spa.1781589950.txt.gz · Last modified: by phong2018