A production-ready OpenID Connect (OIDC) authentication server built with Node.js, Express, and Drizzle ORM. This server provides a centralized authentication service supporting the Authorization Code flow for third-party application integration.
- OIDC Discovery: Implementation of
.well-known/openid-configuration. - Authorization Code Flow: Secure exchange of authorization codes for access and refresh tokens.
- PKCE (Proof Key for Code Exchange): Enhanced security for public and private clients, protecting against authorization code injection.
- Refresh Token Rotation: Automatic token replacement and reuse detection to mitigate session hijacking risks.
- Application Management: Administrative interface for registering third-party applications.
- Dynamic Context: Authentication and registration screens that adapt based on the requesting application.
- JWT Issuance: RSA256 signed tokens for secure identity propagation.
- User Management: Integrated sign-in and sign-up processes with salt-based password hashing.
- Runtime: Node.js with Express.
- Database: PostgreSQL with Drizzle ORM.
- Cryptography:
node-josefor JWKS andjsonwebtokenfor signing. - Frontend: Vanilla JavaScript and CSS served as static assets.
graph TD
User((User))
App["Third-Party App"]
AuthServer["Auth Server (Express)"]
DB[("PostgreSQL")]
Cert["RSA Keys (cert/)"]
User <--> App
App <--> AuthServer
AuthServer <--> DB
AuthServer <--> Cert
subgraph "Auth Server (Node.js)"
Endpoints["OIDC Endpoints"]
Logic["Auth & Token Logic"]
Drizzle["Drizzle ORM"]
end
sequenceDiagram
participant User
participant App as Third-Party App
participant Auth as Auth Server
participant DB as Database
Note over User, Auth: 1. Authorization Request (PKCE)
App->>Auth: GET /o/authenticate (client_id, code_challenge, ...)
Auth-->>User: Sign-in / Sign-up Page
User->>Auth: POST /o/authenticate/sign-in (creds, code_challenge)
Auth->>DB: Verify User & Store Auth Code + Challenge
Auth-->>App: 302 Redirect (code)
Note over App, Auth: 2. Token Exchange
App->>Auth: POST /o/tokeninfo (code, code_verifier, client_secret)
Auth->>DB: Validate Code, Verifier & Application
DB-->>Auth: Valid
Auth->>DB: Store Refresh Token Hash
Auth-->>App: 200 OK (access_token, refresh_token)
Note over App, Auth: 3. Refresh Token Rotation
App->>Auth: POST /o/tokeninfo (grant_type=refresh_token, refresh_token)
Auth->>DB: Validate Refresh Token (not consumed/revoked)
Auth->>DB: Mark old token consumed, Issue new Refresh Token
Auth-->>App: 200 OK (access_token, new_refresh_token)
Note over App, Auth: 4. Reuse Detection (Security)
App->>Auth: POST /o/tokeninfo (grant_type=refresh_token, OLD_refresh_token)
Auth->>DB: Detect reuse (consumedAt is set)
Auth->>DB: Revoke all tokens for User/App session
Auth-->>App: 401 Unauthorized (Reuse detected)
- Node.js (v18 or higher)
- PostgreSQL Database
- npm or yarn
-
Clone the repository:
git clone <repository-url> cd oidc_auth
-
Install dependencies:
npm install
-
Configure environment variables: Create a
.envfile in the root directory with the following variables:DATABASE_URL=postgres://user:password@localhost:5432/oidc_auth PORT=8000
-
Generate RSA Key Pair: The server requires RSA keys for signing and verifying JWTs. Run the provided key generator:
node keygen.js
This will create a
cert/directory withprivate.keyandpublic.key.
The project uses Drizzle ORM for database management.
-
Generate migrations:
npm run db:generate
-
Apply migrations:
npm run db:migrate
Development mode with hot-reloading:
npm run devThe server will be available at http://localhost:8000.
- Navigate to
http://localhost:8000/admin. - Provide the Application Display Name, Application URL, and Redirect URI.
- Upon registration, the system will generate a
client_idandclient_secret. - Store the
client_secretsecurely; it is required for token exchange.
Redirect users to the authentication endpoint:
GET /o/authenticate?client_id=<CLIENT_ID>&redirect_uri=<REDIRECT_URI>&state=<STATE>
The user signs in or creates an account. The UI will display the name of the requesting application.
Upon successful authentication, the server redirects to the specified redirect_uri with a short-lived authorization code:
HTTP 302 Redirect: <REDIRECT_URI>?code=<AUTH_CODE>&state=<STATE>
The third-party application exchanges the code for tokens:
POST /o/tokeninfo
Payload:
{
"code": "<AUTH_CODE>",
"client_secret": "<CLIENT_SECRET>"
}Response:
{
"access_token": "<JWT>",
"refresh_token": "<TOKEN>",
"token_type": "Bearer",
"expires_in": 3600
}GET /.well-known/openid-configuration: Returns OIDC server metadata.GET /.well-known/jwks.json: Returns public keys for JWT verification.
GET /o/authenticate: Serves the sign-in page.GET /o/signup: Serves the registration page.POST /o/authenticate/sign-in: Handles user login.POST /o/authenticate/sign-up: Handles user registration.GET /o/userinfo: Returns authenticated user profile data (requires Bearer token).
GET /o/application-info: Fetches public metadata for a specific application.GET /health: System health check endpoint.