Single Sign-On (SSO) / OAuth2 Server Feature — Central Membership & SSO Hub
1. Document Purpose
This document explains the specifications of the SSO / OAuth2 Server feature run by the Membership Hub. The SSO Hub functions as an Identity Provider (IdP) that issues authentication tokens to SaaS applications within the ecosystem.
2. Membership Hub as OAuth2 Server
Membership Hub implements OAuth2 Authorization Code Flow with PKCE as the authentication standard.
Hub plays the role of:
├── Authorization Server → Issues authorization code & token
├── Identity Provider → Stores & validates member identities
└── Resource Server → Provides member profile & license information
Each SaaS application that joins the ecosystem plays the role of:
SaaS application plays the role of:
└── OAuth2 Client (Relying Party) → Requests access on behalf of the member
3. OAuth2 Endpoints
3.1 Main Endpoints
| Endpoint | Method | Function |
|---|---|---|
/oauth/authorize |
GET |
Displays login & consent page |
/oauth/token |
POST |
Exchanges authorization code for token |
/oauth/token |
POST |
Refreshes access token using refresh token |
/oauth/revoke |
POST |
Revokes token |
/oauth/logout |
GET |
Ends Hub session |
/oauth/userinfo |
GET |
Retrieves member profile information |
/.well-known/oauth-authorization-server |
GET |
OAuth2 server metadata |
/.well-known/jwks.json |
GET |
Public key for JWT verification |
3.2 /oauth/authorize — Authorization Endpoint
Request:
GET /oauth/authorize
?client_id=CLIENT_ID
&redirect_uri=https://app.noto.com/auth/callback
&response_type=code
&scope=profile:read license:read
&state=RANDOM_CSRF_STATE
&code_challenge=PKCE_CODE_CHALLENGE
&code_challenge_method=S256
Parameters:
| Parameter | Required | Description |
|---|---|---|
client_id |
Yes | Registered OAuth2 client ID |
redirect_uri |
Yes | Registered callback URL |
response_type |
Yes | Must be code |
scope |
Yes | Requested scope |
state |
Yes | Random value for CSRF prevention |
code_challenge |
Yes (PKCE) | Hash of code_verifier |
code_challenge_method |
Yes (PKCE) | Must be S256 |
Successful Response — Redirect to redirect_uri:
https://app.noto.com/auth/callback
?code=AUTH_CODE
&state=RANDOM_CSRF_STATE
Error Response — Redirect with error:
https://app.noto.com/auth/callback
?error=access_denied
&error_description=License+not+found
&state=RANDOM_CSRF_STATE
3.3 /oauth/token — Token Endpoint
3.3.1 Exchanging Authorization Code for Token
Request:
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTH_CODE
&redirect_uri=https://app.noto.com/auth/callback
&client_id=CLIENT_ID
&client_secret=CLIENT_SECRET
&code_verifier=PKCE_CODE_VERIFIER
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "refresh_token_string",
"scope": "profile:read license:read"
}
3.3.2 Refreshing Access Token
Request:
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=REFRESH_TOKEN
&client_id=CLIENT_ID
&client_secret=CLIENT_SECRET
Response: Same as new token response.
3.4 /oauth/userinfo — UserInfo Endpoint
Request:
GET /oauth/userinfo
Authorization: Bearer ACCESS_TOKEN
Response:
{
"sub": "member_id_123",
"name": "Budi Santoso",
"email": "budi@email.com",
"email_verified": true,
"picture": null
}
4. JWT Access Token Structure
JWT is signed using RS256 (RSA Signature with SHA-256).
4.1 Header
{
"alg": "RS256",
"typ": "JWT",
"kid": "key-id-001"
}
4.2 Payload (Claims)
{
"sub": "member_id_123",
"name": "Budi Santoso",
"email": "budi@email.com",
"email_verified": true,
"product": "NTO",
"license_id": "NTO-A1B2-C3D4-E5F6",
"tier": "free",
"license_status": "active_free",
"expires_at": null,
"iss": "https://hub.domain.com",
"aud": "noto-client-id",
"iat": 1720000000,
"exp": 1720003600,
"jti": "unique-token-id"
}
4.3 Claim Explanation
| Claim | Type | Description |
|---|---|---|
sub |
string | Unique member ID (Subject) |
name |
string | Member full name |
email |
string | Member email |
email_verified |
boolean | Whether email is verified |
product |
string | SaaS product code that allows login |
license_id |
string | Member license-ID for this product |
tier |
string | Active package (free, pro, business) |
license_status |
string | License status (active, active_free, grace_period, suspended) |
expires_at |
timestamp / null | License expiration time (null = free forever) |
iss |
string | Issuer: Hub URL |
aud |
string | Audience: client_id of SaaS application |
iat |
timestamp | Token issuance time (Issued At) |
exp |
timestamp | Token expiry time (Expiry) |
jti |
string | Unique token ID (JWT ID) for revocation |
5. Available Scopes
| Scope | Accessible Data |
|---|---|
profile:read |
Name, email, profile picture |
license:read |
License-ID, tier, license status, expired_at |
openid |
Sub (member ID) — for OpenID Connect compatibility |
6. Registering OAuth2 Client (SaaS Application)
Each SaaS application that joins the ecosystem must register itself as an OAuth2 Client.
6.1 Required Information
| Field | Example | Description |
|---|---|---|
client_name |
NOTO | Application name |
product_code |
NTO | Product code in the system |
redirect_uris |
https://app.noto.com/auth/callback |
List of allowed callback URLs |
post_logout_redirect_uris |
https://app.noto.com/logout |
URL after Hub logout |
logo_uri |
Application logo URL | Displayed on consent page |
homepage_uri |
https://app.noto.com |
Application homepage URL |
6.2 Registration Result
After registration, the system issues:
{
"client_id": "noto-client-id-abc123",
"client_secret": "secret_key_xyz789"
}
client_secret is only shown once. Save it securely.
7. Rotating Public Key (JWKS)
- Membership Hub uses a RSA key pair (private key + public key).
- Private key is used to sign JWT (only on the server).
- Public key is published through the
/.well-known/jwks.jsonendpoint.
Example JWKS response:
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"kid": "key-id-001",
"alg": "RS256",
"n": "...",
"e": "AQAB"
}
]
}
Key rotation:
- Key can be rotated periodically for security.
- Old key remains available in JWKS for validating active tokens.
- After all old tokens expire, old key is removed from JWKS.
- SaaS applications are recommended to fetch JWKS from the endpoint (not hardcode) and cache it.
8. Hub Session
- After a member successfully logs in to Hub, a Hub session is created on the server.
- Hub session allows members to log in to multiple applications without re-entering credentials (true SSO).
- Hub session has an expiration time (e.g., 8 hours from last