oicd-server is a minimal, zero-dependency OpenID Connect (OIDC) Authorization Server built from scratch using only the Bun runtime and standard Node.js crypto modules.
Its primary purpose is to provide a clear, simple, and auditable implementation of the OAuth 2.1 (draft)-compliant Authorization Code Flow with PKCE. It is designed for students, developers, and security enthusiasts who want to understand how OIDC and modern OAuth 2.0 work under the hood.
This server is designed exclusively for educational purposes and local development. It is NOT intended for production use.
It intentionally omits many production-grade features to keep the core logic simple and easy to read.
Key Limitations:
- In-Memory Storage: All users, clients, and codes are stored in memory and are lost on restart.
- Missing Features: Does not include refresh tokens, user consent screens, or robust session management.
- Simplified Security: Uses basic (though secure) crypto implementations. Does not include rate-limiting, comprehensive error handling, or key rotation.
- ✅ OAuth 2.1 Compliant Flow: Implements Authorization Code Flow with PKCE (RFC 7636).
- ✅ Zero External Dependencies: Built entirely with the Bun runtime and Node.js
crypto. - ✅ Standard OIDC Discovery: Provides
.well-known/openid-configurationfor auto-discovery. - ✅ JSON Web Tokens (JWT): Issues
id_tokenandaccess_tokensigned with RS256. - ✅ JWKS Endpoint: Serves public keys at
.well-known/jwks.jsonfor token verification. - ✅ Core Endpoints: Implements
/authorize,/token, and/userinfo.
Prerequisite: Bun (v1.0+) must be installed.
- Clone the Repository
git clone [https://github.com/andreacanton/oicd-server.git](https://github.com/andreacanton/oicd-server.git)
cd oicd-server- Install & Run
bun install
bun run devThe server will start at http://localhost:3000 with hot-reload enabled.
| Script | Description |
|---|---|
bun run dev |
Starts the development server with auto-reload. |
bun run build |
Creates a production-ready build. |
bun run start |
Runs the production build. |
bun test |
Executes all unit tests. |
All test data is defined in-memory in src/index.ts.
| Credential | Value |
|---|---|
| Client ID | sample-client |
| Client Secret | sample-secret |
| Redirect URI | http://localhost:3001/callback |
| Credential | Value |
|---|---|
| Username | testuser |
| Password | password123 |
test@example.com |
| Method | Path | Description |
|---|---|---|
GET |
/.well-known/openid-configuration |
OIDC Discovery Document. Provides metadata about all other endpoints. |
GET |
/.well-known/jwks.json |
JSON Web Key Set. Provides the public keys to verify JWT signatures. |
GET |
/authorize |
Authorization Endpoint. Initiates the login flow and displays a login form. |
POST |
/token |
Token Endpoint. Exchanges an authorization code for an id_token and access_token. |
GET |
/userinfo |
UserInfo Endpoint. Returns user profile information (requires a valid access_token). |
Here is how to perform a complete authorization flow manually.
You need a code_verifier (a random string) and a code_challenge (its SHA256 hash). You can use the included Bruno collection or generate them:
# 1. Generate a random verifier
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=+/' | head -c 43)
# 2. Generate the challenge (URL-safe SHA256)
CODE_CHALLENGE=$(echo -n $CODE_VERIFIER | shasum -a 256 | cut -d' ' -f1 | xxd -r -p | base64 | tr -d '=+/' | tr '/+' '_-')
# You can check them:
echo $CODE_VERIFIER
echo $CODE_CHALLENGEConstruct the authorization URL and open it in your browser.
http://localhost:3000/authorize?
client_id=sample-client
&redirect_uri=http://localhost:3001/callback
&response_type=code
&scope=openid profile email
&state=somerandomstate123
&code_challenge=YOUR_CODE_CHALLENGE
&code_challenge_method=S256
Log in with the test user (testuser / password123). The server will redirect you to:
http://localhost:3001/callback?code=AUTHORIZATION_CODE&state=somerandomstate123
Copy the AUTHORIZATION_CODE from the URL.
Now, make a POST request to the /token endpoint using the code and your original code_verifier.
curl -X POST 'http://localhost:3000/token' \
-H 'Content-Type: application/json' \
-d '{
"grant_type": "authorization_code",
"code": "THE_AUTHORIZATION_CODE_FROM_STEP_2",
"client_id": "sample-client",
"client_secret": "sample-secret",
"redirect_uri": "http://localhost:3001/callback",
"code_verifier": "YOUR_ORIGINAL_CODE_VERIFIER_FROM_STEP_1"
}'The server will respond with your tokens:
{
"access_token": "eyJhbGc...",
"id_token": "eyJhbGc...",
"token_type": "Bearer",
"expires_in": 900
}Note: The
client_secretis included here because thesample-clientis confidential. A public client (like a mobile app) would omit theclient_secret, and the server would validate the request using only PKCE.
Use the access_token to retrieve user information from the /userinfo endpoint.
# Replace ACCESS_TOKEN with the one from the previous step
curl -H 'Authorization: Bearer ACCESS_TOKEN' \
http://localhost:3000/userinfoThe server will respond with the user's data:
{
"sub": "user-1",
"email": "test@example.com",
"name": "testuser"
}This is an educational project, and contributions are welcome! The primary goal is to maintain simplicity and clarity.
- Zero-Dependency Policy: Please do not add any external
npmpackages. - Clarity over Features: Code should be easy to read and well-commented.
- Add Tests: Please add tests for any new logic.
Feel free to open an issue to discuss a potential change or submit a Pull Request.
This project is licensed under the GPL-3.0. See the LICENSE file for details.