A small example OAuth 2.0 / OpenID Connect authorization server built with Express and Authlete SDK. This project demonstrates the authorization, token, userinfo, introspection and revocation endpoints and includes simple session-based login and consent pages for interactive flows.
This repository is intended as a learning/demo server β it is not production hardened. Use it to explore the HTTP endpoints and how an OAuth2 flow can be implemented using Authlete APIs.
- Authorization endpoint (GET/POST)
- Token endpoint (POST, accepts JSON and form-encoded payloads)
- UserInfo endpoint (POST)
- Introspection and Revocation endpoints (POST)
- OpenID Connect Discovery (
.well-known/openid-configuration) - JWKS endpoint (JSON Web Key Set)
- Session-based login and consent UI (server-side rendered with EJS)
- Interactive routes listing UI with curl examples
- RP-initiated and backchannel logout
- Structured logging with request tracking (Winston + Morgan + request-id)
- Node.js 18+ (or latest stable)
- npm
- An Authlete account and service configured (optional if you want to call Authlete APIs)
- Install dependencies
npm --prefix server install && npm --prefix client install- Create a
.envfile in the project root or set environment variables. Minimal example.env:
# Authlete Configuration
AUTHLETE_BEARER_TOKEN=your_authlete_bearer_token_here
AUTHLETE_BASE_URL=https://{region}.authlete.com # example: https://us.authlete.com
AUTHLETE_SERVICE_ID=your_authlete_service_id_here
# Server Configuration
PORT=3000
NODE_ENV=development
# Session Configuration
SESSION_SECRET=your_session_secret_here
# If ACCESS_TOKEN_TYPE=jwt:
JWT_PUBLIC_KEY_PEM="-----BEGIN PUBLIC KEY-----\nMFkw......\n-----END PUBLIC KEY-----\n"
JWT_PRIVATE_KEY_PEM="-----BEGIN PRIVATE KEY-----\nMEE......\n-----END PRIVATE KEY-----\n"
JWT_SELF_SIGNED_CERT_PEM="-----BEGIN CERTIFICATE-----\nMII......\n-----END CERTIFICATE-----\n"
JWT_ISSUER= #https://example.test/
# JWKS URI
JWKS_URI= #jwks URI
# Logging (optional)
LOG_LEVEL=debug # debug, info, warn, error
MORGAN_FORMAT=combined # combined, short, dev, etc.
- Start the server in development mode
npm --prefix server run devThe server listens on port 3000 by default (configurable via PORT env var). Open http://localhost:3000/api/routes to see the available routes and example curl commands.
src/
βββ app.ts # Express app setup (middleware, routes, error handling)
βββ server.ts # Entry point
βββ config/
β βββ app.config.ts # App configuration
β βββ authlete.config.ts # Authlete SDK setup
βββ controllers/ # Request handlers for each endpoint
β βββ authorization-response.controller.ts
β βββ authorization-response.handler.ts # Shared Authlete response handler
β βββ authorization.controller.ts
β βββ token-exchange-response.handler.ts # Shared Authlete response handler
β βββ token-fail-response.handler.ts # Shared Authlete response handler
β βββ token-fail.controller.ts
β βββ token-issue-response.handler.ts # Shared Authlete response handler
β βββ token-issue.controller.ts
β βββ token.controller.ts
β βββ token.management.controller.ts
β βββ session.controller.ts # Login/consent handlers
β βββ userinfo.controller.ts
β βββ revocation.controller.ts
β βββ introspection-standard.controller.ts
β βββ introspection.controller.ts
β βββ discovery.controller.ts
β βββ userinfo-issue-response.handler.ts # Shared Authlete response handler
β βββ jwks.controller.ts
β βββ logout.controller.ts
β βββ userinfo-issue.controller.ts
β βββ authorization-response.handler.ts # Shared Authlete response handler
βββ middleware/
β βββ session.ts # express-session configuration
β βββ errorHandler.ts # Global error handler (renders HTML/JSON)
βββ routes/ # Route definitions
βββ services/ # Authlete API calls and business logic
βββ utils/
createLocalJWT.ts
β βββ logger.ts # Winston logger configuration
β βββ crypto.ts # PKCE helpers
β βββ jwksClient.ts # JWKS generator utilities
β βββ jwtAssertionValidator.ts # JWKS generator utilities
β βββ createLocalJWT.ts # JWT generator utilities
βββ types/
β βββ express.d.ts # Express Request augmentation (req.id, req.logger)
β βββ express-session.d.ts # Session data typing
βββ views/ # EJS templates
βββ index.ejs
βββ login.ejs
βββ consent.ejs
βββ logout.ejs
βββ error.ejs
βββ routes.ejs
βββ partials/head.ejs
The server uses EJS for server-side rendering:
index.ejsβ Default pagelogin.ejsβ Sign-in form for the interactive authorization flowconsent.ejsβ Consent page showing requested scopes with approve/deny actionserror.ejsβ Error page (shown for HTTP errors)logout.ejsβ Logout confirmation pageroutes.ejsβ Interactive routes listing with curl examplespartials/head.ejsβ Shared styles and head content
The server uses Winston for structured application logging and Morgan for HTTP access logs.
Log files:
logs/app-YYYY-MM-DD.logβ Application logs (info and above)logs/error-YYYY-MM-DD.logβ Error logs (daily rotation, 30-day retention)
Console output:
- Development: colorized, human-readable format
- Production: JSON format with timestamps
Each request is assigned a unique ID via express-request-id middleware, which is included in all logs for easy tracing. Use LOG_LEVEL env var to control verbosity (default: debug in development, info in production).
The server exposes the following endpoints:
| Method | Path | Description |
|---|---|---|
| GET | /api/authorization |
Authorization endpoint (interactive) |
| POST | /api/token |
Token endpoint |
| POST | /api/userinfo |
UserInfo endpoint |
| POST | /api/introspection |
Token introspection |
| POST | /api/introspection/standard |
Token introspection (RFC 7662) |
| POST | /api/revocation |
Token revocation |
| GET | /api/session/login |
Login page |
| POST | /api/session/login |
Login submission |
| GET | /api/session/consent |
Consent page |
| POST | /api/session/consent |
Consent submission |
| GET | /api/.well-known/jwks.json |
JSON Web Key Set |
| GET | /api/.well-known/openid-configuration |
OpenID Configuration |
| GET | /api/token/list |
Token Operations Endpoint |
| POST | /api/token/create |
Token Operations Endpoint |
| PATCH | /api/token/update |
Token Operations Endpoint |
| DELETE | /api/token/delete |
Token Operations Endpoint |
| POST | /api/token/revoke |
Token Operations Endpoint |
| POST | /api/token/reissue |
Token Operations Endpoint |
| GET | /api/logout |
RP-initiated logout |
| POST | /api/backchannel_logout |
Backchannel logout |
| GET | /api/routes |
Routes listing UI |
| GET | /api/routes.json |
Routes as JSON |
These examples assume the server is running on http://localhost:3000 and that you are driving the flow from a client application. Replace client IDs, secrets, and codes with real values from your environment.
- Authorization Code (interactive)
- Step A: The client directs the user-agent to the authorization endpoint. Example (open in browser):
http://localhost:3000/authorization?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=http://localhost:3000/callback&scope=openid%20profile%20email&state=xyz
-
Step B: The server will show login (
/session/login) and consent (/session/consent) pages. After the user approves, the server will redirect back to the client'sredirect_uriwithcodeandstate. -
Step C: Exchange code for token (server-to-server call):
Form-encoded example:
curl -X POST http://localhost:3000/api/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Authorization: Basic BASE64(client_id:client_secret)" \
-d "grant_type=authorization_code" \
-d "code=AUTHORIZATION_CODE" \
-d "redirect_uri=http://localhost:3000/callback"JSON example (application/json):
curl -X POST http://localhost:3000/api/token \
-H "Content-Type: application/json" \
-d '{
"grant_type":"authorization_code",
"code":"0iRBC1hfoRDkKPQZagUX-N4PuypeQWhrGIeArW-VUlk",
"redirect_uri":"http://localhost:3000",
"code_verifier":"186c6529dc72785ed16bb7336a99ae23b22dce64a5def65ebf57c1ab",
"clientId":"3322138582"
}'- Resource Owner Password Credentials (for testing only)
curl -X POST http://localhost:3000/api/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password" \
-d "username=admin" \
-d "password=password" \
-d "scope=openid profile" \
-d "client_id=***" \
-d "client_secret=****"curl -X POST http://localhost:3000/api/token \
-H 'Authorization: Basic BASE64(client_id:client_secret)' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=password&response_type=token&username=admin&password=password&scope=email'- Introspection
curl -X POST http://localhost:3000/api/introspection \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Authorization: Basic BASE64(client_id:client_secret)" \
-d "token=ACCESS_OR_REFRESH_TOKEN"curl -X POST http://localhost:3000/api/introspection \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Authorization: Basic BASE64(client_id:client_secret)" \
-d "token=ACCESS_OR_REFRESH_TOKEN"- Revocation
curl -X POST http://localhost:3000/api/revocation \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Authorization: Basic BASE64(client_id:client_secret)" \
-d "token=ACCESS_OR_REFRESH_TOKEN"- UserInfo
curl -X POST http://localhost:3000/api/userinfo \
-H "Content-Type: application/json" \
-d '{"token":"YOUR_ACCESS_TOKEN"}'The project uses express-session to store login/authorization context across interactive steps:
- User login state (
req.session.user) - OAuth authorization details (
req.session.authorizationwith ticket, clientId, scopes, etc.) - Custom session options via environment or factory configuration
For development: In-memory session store (default). For production: Replace with a persistent store like Redis or MongoDB to support multi-process deployments.
See src/middleware/session.ts for configuration details.
The global error handler (src/middleware/errorHandler.ts):
- Renders user-friendly HTML error pages (with stack traces in development)
- Returns JSON for API requests (Accept header detection)
- Logs all errors with request context via structured logger
- Open
http://localhost:3000/api/routesto see interactive routes listing and copy-paste curl examples - Check
logs/directory for application and error logs (automatically rotated daily) - Use
LOG_LEVEL=debugenv var to see verbose debug output - Monitor
req.loggeroutput in services/controllers for request-specific tracing (includes request ID)
This example is provided for educational purposes. It is not production-ready and omits many security best practices (CSRF protection, input validation, secure cookie settings for production, etc.).
- Dockerfile for containerized deployment
- Automated tests (unit and integration)
- Persistent session store integration (Redis)
- Enhanced CSRF/CORS protection
- Admin dashboard for service management
-
Go to OAuth Tools
-
Use Curity Playground, which provides a UI to test OAuth 2.0 flows with the demo server.
-
Either enter the demo server metadata manually or fetch it automatically from the Curity Playground settings (as shown in the screenshots).
-
Start testing the flows.
This project contains a React SPA that plays a role as OAuth 2.0 client (Authorization Code Flow with PKCE using Public Client Type).
inside the CLIENT Directory
cd /client
cp .env.example .env
npm install
npm run build
npm run previewRoot Directory
npm --prefix client install
npm --prefix client run build
npm --prefix client run previewThe SPA will run on http://localhost:3001









