A complete, self-contained OAuth2 implementation demonstrating modern authentication flows with practical examples. Perfect for learning how OAuth2, JWT tokens, and PKCE work in a real-world scenario.
This project demonstrates:
- β OAuth2 Authorization Code Flow with PKCE (Proof Key for Code Exchange)
- β JWT Token generation, validation, and RSA-256 signature verification
- β CSRF Protection via state parameter validation
- β Refresh Tokens for extended sessions
- β CORS configuration for cross-origin requests
- β JWT Decoder to inspect token claims and structure
- β Multi-service Architecture (IDP, Backend API, Frontend)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β OAuth2 Learning Lab β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Frontend (React) Backend (Go) IDP (Go) β
β Port 3000 Port 8081 Port 8080 β
β ββββββββββββββββ ββββββββββββββββ βββββββββββ β
β β Login Page β β /callback β β/authorizeβ β
β β Dashboard β β /api/profile βββββββΊβ /token β β
β β Token ββββββββββΊβ /logout β β /userinfoβ β
β β Debugger β ββββββββββββββββ βββββββββββ β
β ββββββββββββββββ JWT Validator SQLite DB β
β CORS Handler RSA Keys β
β β β
β JWKS β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- Docker & Docker Compose
- Make (or run commands manually)
- Modern web browser
# Clone the repository
git clone https://github.com/yourusername/oauth2-learning-lab.git
cd oauth2-learning-lab
# Generate RSA keys for JWT signing
make generate-keys
# Build Docker images
make build
# Start all services
make up
# View logs
make logs| Service | URL | Purpose |
|---|---|---|
| Frontend | http://localhost:3000 | React app - Click "Log in with OAuth2" |
| Backend API | http://localhost:8081 | REST API - /callback, /api/profile |
| IDP Server | http://localhost:8080 | OAuth2 Identity Provider |
| IDP Login | http://localhost:8080/login | Test user login form |
| JWKS Endpoint | http://localhost:8080/.well-known/jwks.json | Public keys for verification |
User clicks "Log in with OAuth2"
β
Frontend generates PKCE code_challenge
β
Redirects to IDP: /oauth2/authorize?client_id=...&code_challenge=...&state=...
IDP validates client and redirect_uri
β
Creates authorization code
β
Redirects back to Frontend: /callback?code=xxx&state=yyy
Frontend sends code to Backend: /callback?code=xxx&code_verifier=...
β
Backend exchanges code with IDP: POST /oauth2/token
β
IDP verifies PKCE and issues JWT token
β
Backend returns token to Frontend
Frontend calls: GET /api/profile with Authorization: Bearer <token>
β
Backend validates JWT signature using IDP's public keys
β
Returns user profile data
Test Users (IDP Login at http://localhost:8080/login)
- Email: [email protected] | Name: Demo User
- Email: [email protected] | Name: Test User
- Frontend Client:
myshop-frontend(no secret for SPA) - Backend Client:
myshop-backendwith secretbackend-secret-change-me
Once logged in, navigate to the Dashboard and click "Show Token Details" to see:
{
"iss": "http://idp:8080", // Issuer
"sub": "user-001", // Subject (User ID)
"aud": "myshop-frontend", // Audience
"email": "[email protected]", // Email claim
"name": "Demo User", // Name claim
"scope": "openid profile email", // Scopes granted
"iat": 1706702449, // Issued At
"exp": 1706706049, // Expiration
"alg": "RS256" // Algorithm
}| Method | Endpoint | Purpose |
|---|---|---|
| GET | /health |
Health check |
| GET | /callback?code=...&code_verifier=...&redirect_uri=... |
OAuth callback handler |
| GET | /api/profile |
Get user profile (requires Bearer token) |
| GET | /api/userinfo |
Get user info (requires Bearer token) |
| GET | /logout |
Clear session |
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /oauth2/authorize?client_id=...&code_challenge=... |
Start authorization |
| POST | /oauth2/token |
Exchange code for token |
| POST | /oauth2/refresh |
Refresh access token |
| GET | /oauth2/userinfo |
Get user info from token |
| GET | /.well-known/jwks.json |
Get public keys for verification |
oauth2-learning-lab/
βββ frontend/ # React SPA
β βββ src/
β β βββ services/
β β β βββ auth.js # OAuth2 flow implementation
β β β βββ tokenDecoder.js # JWT decoder utility
β β βββ components/
β β β βββ ProtectedRoute.jsx
β β β βββ TokenDebugger.jsx # π Token inspection
β β βββ pages/
β β βββ Login.jsx
β β βββ Callback.jsx
β β βββ Dashboard.jsx
β βββ package.json
β βββ Dockerfile
β
βββ backend/ # Go REST API
β βββ main.go # API server
β βββ handlers/
β β βββ auth.go # OAuth callback & token exchange
β β βββ profile.go # User profile endpoint
β β βββ cors.go # CORS middleware
β βββ middleware/
β β βββ jwt..go # JWT validation
β βββ Dockerfile
β
βββ idp/ # Identity Provider (OAuth2 Server)
β βββ main.go
β βββ auth/
β β βββ jwt.go # JWT creation & signing
β β βββ crypto.go # PKCE functions
β βββ handlers/
β β βββ authorize.go # Authorization endpoint
β β βββ token.go # Token endpoint
β β βββ login.go # Login form (new!)
β βββ storage/
β β βββ sqlite.go # Authorization codes & tokens
β βββ Dockerfile
β
βββ scripts/
β βββ generate-keys.sh # Generate RSA keypair
β
βββ secrets/
β βββ private.pem # Private key (sign tokens)
β βββ public.pem # Public key (verify tokens)
β
βββ docker-compose.yaml # Multi-container setup
βββ makefile # Convenient commands
βββ ENHANCEMENTS.md # Feature details
βββ README.md # This file
Why it matters: Prevents authorization code interception attacks in SPAs
- Frontend generates random
code_verifier(43 characters) - SHA256 hash β
code_challenge - Code exchange requires original
code_verifier - IDP verifies: SHA256(verifier) == challenge
- Frontend generates random
stateparameter - Stores in sessionStorage
- On callback, verifies state matches
- Prevents attackers from hijacking OAuth flow
header.payload.signature
Header (encoded): {"alg":"RS256","typ":"JWT","kid":"default"}
Payload (encoded): {"iss":"...","sub":"...","email":"...","exp":...}
Signature (crypto): RSA256(header.payload, private_key)
- IDP signs tokens with private key
- Backend verifies with IDP's public key
- Backend fetches public keys from JWKS endpoint
- Can verify token without trusting network
make help # Show all commands
make generate-keys # Generate RSA keypair
make build # Build all Docker images
make up # Start all services
make down # Stop all services
make logs # View service logs
make clean # Remove volumes and containers
make test # Run tests (if available)Services:
- idp (port 8080) - OAuth2 Identity Provider
- backend (port 8081) - Backend API
- frontend (port 3000) - React SPA
Volumes:
- idp_data - SQLite database for authorization codes
- secrets/ - RSA keys for signing
Networks:
- oauth2 - Internal network for service communication- JWT signature verification with RSA-256
- PKCE for authorization code flow
- State parameter for CSRF protection
- CORS configuration
- Bearer token validation
- HTTPOnly cookies ready for refresh tokens
This is a learning project. Production requirements:
- HTTPS/TLS for all endpoints
- Database encryption for sensitive data
- Rate limiting on auth endpoints
- Password hashing (bcrypt/scrypt)
- Session management & timeout
- Token revocation lists
- Comprehensive logging & monitoring
- Input validation & sanitization
- Authorization Code Flow
- PKCE (Proof Key for Code Exchange)
- JWT Token Generation & Validation
- Refresh Tokens
- JWKS (JSON Web Key Set)
- CORS Configuration
- Login form at IDP
- Dashboard with user profile
- Logout functionality
- State validation (CSRF protection)
- Token debugger/inspector
- Error handling
- Docker Compose setup
- Health checks
- Detailed logging
- Makefile commands
- Token inspection UI
- Comprehensive README
- User registration endpoint
- Password authentication
- Consent screen
- Scope selection
- Silent refresh (automatic token renewal)
- Rate limiting
- Multi-factor authentication
- Social login (Google, GitHub)
- Database migrations
- API documentation (Swagger)
This is an educational project. Feel free to:
- Fork and modify for learning
- Add more OAuth2 flows (Implicit, Client Credentials, etc.)
- Implement additional features
- Improve documentation
- Add tests
MIT License - Use freely for educational purposes
- Run the project:
make up - Access frontend: http://localhost:3000
- Click login: Trigger OAuth2 flow
- View token: Click "Show Token Details" on dashboard
- Explore code: Read through the implementations
- Experiment: Modify and test the flows
Q: Why is the authorization auto-approved? A: This is a POC. In production, users would see a consent screen.
Q: Can I use this in production? A: No - it's for learning only. Lacks production security measures.
Q: How do I modify the token claims?
A: Check idp/handlers/token.go - add claims to the JWT payload.
Q: Why does token verification need the public key? A: RSA public keys verify signatures created with private keys. Backend doesn't trust tokens - it verifies them cryptographically.
Q: What's the difference between PKCE and CSRF state? A: PKCE prevents code interception. State prevents misrouted callbacks. Use both!
Happy Learning! π
For questions or suggestions, open an issue or create a pull request.
Made with β€οΈ for OAuth2 learners