Newer
Older
springboot-auth201 / EXAMPLES.md
@agalyaramadoss agalyaramadoss on 29 Nov 10 KB first commit

OAuth 2.1 Quick Start Examples

PKCE Code Generation (Shell Script)

# Generate code verifier
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '+/' | tr '=' '_' | cut -c1-43)

# Generate code challenge
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -binary -sha256 | openssl base64 | tr -d '+/' | tr '=' '_')

echo "Code Verifier: $CODE_VERIFIER"
echo "Code Challenge: $CODE_CHALLENGE"

PKCE Code Generation (Python)

import base64
import hashlib
import os

# Generate code verifier
code_verifier = base64.urlsafe_b64encode(os.urandom(32)).decode('utf-8').rstrip('=')

# Generate code challenge
code_challenge = base64.urlsafe_b64encode(
    hashlib.sha256(code_verifier.encode('utf-8')).digest()
).decode('utf-8').rstrip('=')

print(f"Code Verifier: {code_verifier}")
print(f"Code Challenge: {code_challenge}")

PKCE Code Generation (JavaScript/Node.js)

const crypto = require('crypto');

// Generate code verifier
const codeVerifier = base64URLEncode(crypto.randomBytes(32));

// Generate code challenge
const hash = crypto.createHash('sha256').update(codeVerifier).digest();
const codeChallenge = base64URLEncode(hash);

function base64URLEncode(str) {
    return str.toString('base64')
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
}

console.log('Code Verifier:', codeVerifier);
console.log('Code Challenge:', codeChallenge);

Complete Authorization Code Flow Example

Step 1: Start Authorization

# Generate PKCE
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '+/' | tr '=' '_' | cut -c1-43)
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -binary -sha256 | openssl base64 | tr -d '+/' | tr '=' '_')

# Open in browser
open "http://localhost:9000/oauth2/authorize?response_type=code&client_id=public-client&redirect_uri=http://127.0.0.1:8080/authorized&scope=openid%20profile%20email%20read%20write&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256&state=xyz123"

Step 2: Get Tokens

# After login and approval, extract the code from redirect URL
# Then exchange it for tokens

curl -X POST http://localhost:9000/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=YOUR_AUTHORIZATION_CODE" \
  -d "redirect_uri=http://127.0.0.1:8080/authorized" \
  -d "client_id=public-client" \
  -d "code_verifier=$CODE_VERIFIER"

Step 3: Use Access Token

# Get user info
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  http://localhost:9000/userinfo

Step 4: Refresh Token

curl -X POST http://localhost:9000/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=YOUR_REFRESH_TOKEN" \
  -d "client_id=public-client"

Client Credentials Flow (Server-to-Server)

curl -X POST http://localhost:9000/oauth2/token \
  -u confidential-client:secret \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "scope=read write"

Inspect JWT Token

# Copy your access token and decode at jwt.io
# Or use command line:

echo "YOUR_JWT_TOKEN" | cut -d. -f2 | base64 -d | jq '.'

Test with cURL

Get Well-Known Configuration

curl http://localhost:9000/.well-known/oauth-authorization-server | jq '.'

Get JWK Set

curl http://localhost:9000/oauth2/jwks | jq '.'

Token Introspection

curl -X POST http://localhost:9000/oauth2/introspect \
  -u confidential-client:secret \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=YOUR_ACCESS_TOKEN"

Token Revocation

curl -X POST http://localhost:9000/oauth2/revoke \
  -u confidential-client:secret \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=YOUR_ACCESS_TOKEN"

Integration Examples

Spring Boot Resource Server

# application.yml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:9000
@Configuration
@EnableWebSecurity
public class ResourceServerConfig {
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(Customizer.withDefaults())
            );
        return http.build();
    }
}

React SPA with PKCE

// OAuth client configuration
const config = {
    authorizationEndpoint: 'http://localhost:9000/oauth2/authorize',
    tokenEndpoint: 'http://localhost:9000/oauth2/token',
    clientId: 'public-client',
    redirectUri: 'http://localhost:3000/callback',
    scope: 'openid profile email read write'
};

// Generate PKCE
async function generatePKCE() {
    const verifier = generateRandomString(43);
    const challenge = await pkceChallengeFromVerifier(verifier);
    sessionStorage.setItem('code_verifier', verifier);
    return challenge;
}

function generateRandomString(length) {
    const array = new Uint8Array(length);
    crypto.getRandomValues(array);
    return base64URLEncode(array);
}

async function pkceChallengeFromVerifier(verifier) {
    const encoder = new TextEncoder();
    const data = encoder.encode(verifier);
    const hash = await crypto.subtle.digest('SHA-256', data);
    return base64URLEncode(new Uint8Array(hash));
}

function base64URLEncode(buffer) {
    return btoa(String.fromCharCode(...buffer))
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
}

// Start OAuth flow
async function login() {
    const codeChallenge = await generatePKCE();
    const state = generateRandomString(20);
    sessionStorage.setItem('oauth_state', state);
    
    const params = new URLSearchParams({
        response_type: 'code',
        client_id: config.clientId,
        redirect_uri: config.redirectUri,
        scope: config.scope,
        code_challenge: codeChallenge,
        code_challenge_method: 'S256',
        state: state
    });
    
    window.location.href = `${config.authorizationEndpoint}?${params}`;
}

// Handle callback
async function handleCallback() {
    const params = new URLSearchParams(window.location.search);
    const code = params.get('code');
    const state = params.get('state');
    
    if (state !== sessionStorage.getItem('oauth_state')) {
        throw new Error('Invalid state parameter');
    }
    
    const codeVerifier = sessionStorage.getItem('code_verifier');
    
    const tokenResponse = await fetch(config.tokenEndpoint, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: new URLSearchParams({
            grant_type: 'authorization_code',
            code: code,
            redirect_uri: config.redirectUri,
            client_id: config.clientId,
            code_verifier: codeVerifier
        })
    });
    
    const tokens = await tokenResponse.json();
    sessionStorage.setItem('access_token', tokens.access_token);
    sessionStorage.setItem('refresh_token', tokens.refresh_token);
    
    return tokens;
}

Android/Kotlin Example

// Using AppAuth library
val config = AuthorizationServiceConfiguration(
    Uri.parse("http://localhost:9000/oauth2/authorize"),
    Uri.parse("http://localhost:9000/oauth2/token")
)

val request = AuthorizationRequest.Builder(
    config,
    "public-client",
    ResponseTypeValues.CODE,
    Uri.parse("myapp://callback")
)
    .setScope("openid profile email read write")
    .setCodeVerifier(CodeVerifierUtil.generateRandomCodeVerifier())
    .build()

val authService = AuthorizationService(context)
val authIntent = authService.getAuthorizationRequestIntent(request)
startActivityForResult(authIntent, AUTH_REQUEST_CODE)

iOS/Swift Example

// Using AppAuth library
let config = OIDServiceConfiguration(
    authorizationEndpoint: URL(string: "http://localhost:9000/oauth2/authorize")!,
    tokenEndpoint: URL(string: "http://localhost:9000/oauth2/token")!
)

let request = OIDAuthorizationRequest(
    configuration: config,
    clientId: "public-client",
    scopes: ["openid", "profile", "email", "read", "write"],
    redirectURL: URL(string: "myapp://callback")!,
    responseType: OIDResponseTypeCode,
    additionalParameters: nil
)

OIDAuthorizationService.present(request, presenting: self) { response, error in
    guard let response = response else {
        return
    }
    // Exchange authorization code for tokens
}

Common OAuth Parameters

Authorization Request Parameters

Parameter Required Description
response_type Yes Must be "code"
client_id Yes Client identifier
redirect_uri Yes Callback URL (must match registered)
scope No Space-separated list of scopes
state Recommended Random string to prevent CSRF
code_challenge Yes (OAuth 2.1) PKCE challenge
code_challenge_method Yes Must be "S256"

Token Request Parameters (Authorization Code)

Parameter Required Description
grant_type Yes Must be "authorization_code"
code Yes Authorization code from callback
redirect_uri Yes Same as authorization request
client_id Yes Client identifier
code_verifier Yes (OAuth 2.1) PKCE verifier

Token Request Parameters (Refresh Token)

Parameter Required Description
grant_type Yes Must be "refresh_token"
refresh_token Yes Current refresh token
client_id Yes Client identifier
scope No Requested scope (must be subset)

Token Request Parameters (Client Credentials)

Parameter Required Description
grant_type Yes Must be "client_credentials"
scope No Requested scopes
Requires Basic Auth header

Scopes Explained

Scope Description
openid Required for OpenID Connect
profile Access to profile information
email Access to email address
read Read access to resources
write Write access to resources

Token Response Structure

{
  "access_token": "eyJraWQiOiI...",
  "refresh_token": "eyJhbGciOiJ...",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "openid profile email read write",
  "id_token": "eyJhbGciOiJ..."
}

JWT Access Token Claims

{
  "sub": "user",
  "aud": "public-client",
  "nbf": 1701234567,
  "scope": ["openid", "profile", "email", "read", "write"],
  "iss": "http://localhost:9000",
  "exp": 1701235467,
  "iat": 1701234567,
  "jti": "550e8400-e29b-41d4-a716-446655440000"
}