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"
}