A lightweight, production-ready OpenID Connect (OIDC) identity provider written in Go with an embedded React admin UI.
No external key-management tooling needed β RSA key pairs and X.509 certificates are generated in pure Go.
| Feature | Status |
|---|---|
| Authorization Code Flow | β |
| Implicit Flow (id_token, token id_token) | β |
| PKCE (S256 + plain) | β |
| Refresh Token Grant | β |
Token Revocation (/revoke) |
β |
Token Introspection (/introspect) |
β |
| Dynamic Client Registration (RFC 7591/7592) | β |
OpenID Connect Discovery (/.well-known/openid-configuration) |
β |
JWKS Endpoint (/.well-known/jwks.json) with x5c / x5t#S256 |
β |
Scopes: openid, profile, email, address, phone, offline_access |
β |
| Nonce replay protection | β |
auth_time claim |
β |
| Feature | Status |
|---|---|
| RSA 2048-bit key generation (pure Go) | β |
| Self-signed X.509 certificate per key | β |
KID derived from cert SHA-256 fingerprint (x5t#S256, RFC 7517) |
β |
| Configurable certificate validity (default 90 days) | β |
| Key rotation β old keys kept until cert expires | β |
| Generate PKCS#10 CSR for CA submission | β |
| Import CA-signed certificate (replaces self-signed) | β |
JWKS serves all valid keys with x5c + x5t#S256 |
β |
| Backend | Use case |
|---|---|
| JSON file | Development, small deployments |
| MongoDB | Production, high-traffic |
- Modern "Secure Slate" design with light / dark theme
- Dashboard with live statistics
- User management (CRUD, password reset)
- OAuth client management + secret rotation
- Token management β search, filter, revoke
- Signing key management β rotate, generate CSR, import cert
- Audit log viewer
- Server settings
- First-run setup wizard
# JSON-file storage (no external dependencies)
docker-compose --profile json-storage up -d
# MongoDB storage
MONGO_USER=admin MONGO_PASSWORD=secret docker-compose --profile with-mongodb up -dThe server starts at http://localhost:8080.
On first launch, the setup wizard runs automatically at http://localhost:8080/setup.
# Download from GitHub Releases and make executable
chmod +x openid-server-*
# Interactive first-run wizard (creates config + admin user)
./openid-server-* --setup
# Start
./openid-server-*# Install Go + Node dependencies, build frontend, run server
make deps
make runopenid-golang/
βββ cmd/
β βββ root.go # CLI entry point (cobra)
β βββ serve.go # Server startup, route registration
βββ pkg/
β βββ configstore/ # Configuration loading (TOML / env / MongoDB)
β βββ crypto/ # RSA keygen, cert gen, CSR, JWT, PKCE, bcrypt
β βββ handlers/
β β βββ admin.go # Admin REST API (users, clients, keys, tokens, audit)
β β βββ authorize.go # /authorize endpoint
β β βββ bootstrap.go # Setup wizard handler
β β βββ discovery.go # /.well-known/* + JWKS
β β βββ handlers.go # Shared handler wiring
β β βββ token.go # /token, /revoke, /introspect
β β βββ userinfo.go # /userinfo
β βββ middleware/ # JWT auth middleware for OIDC endpoints
β βββ models/ # Data models (User, Client, Token, SigningKey, AuditLogβ¦)
β βββ session/ # Server-side session store + cookie middleware
β βββ storage/
β βββ storage.go # Storage interface
β βββ json.go # JSON file implementation
β βββ mongodb.go # MongoDB implementation
βββ frontend/ # React + TypeScript + Ant Design admin UI
β βββ src/
β βββ pages/ # Dashboard, Users, Clients, Tokens, KeyManagement, AuditLogβ¦
β βββ hooks/ # useApi.ts β all React Query hooks
βββ public/ # Embedded HTML templates (login, consent, setup wizard)
βββ embed.go # go:embed declarations
βββ main.go
βββ Dockerfile
βββ docker-compose.yml
βββ Makefile
The server reads configuration from (in priority order):
- Environment variables
data/config.json(written by setup wizard)- Built-in defaults
| Variable | Default | Description |
|---|---|---|
SERVER_HOST |
0.0.0.0 |
Listen address |
SERVER_PORT |
8080 |
Listen port |
MONGODB_URI |
β | MongoDB connection string (enables MongoDB storage) |
MONGODB_DATABASE |
openid |
MongoDB database name |
When MONGODB_URI is set, all data (config + storage) is persisted to MongoDB.
Without it, the JSON file backend (data/openid.json) is used.
Visit http://localhost:8080/setup (or pass --setup to the binary) to:
- Set the issuer URL
- Choose storage backend
- Create the first admin user
- Optionally pre-create an OAuth client
| Endpoint | Method | Description |
|---|---|---|
/.well-known/openid-configuration |
GET | OIDC Discovery document |
/.well-known/jwks.json |
GET | JSON Web Key Set (all valid keys) |
/authorize |
GET | Authorization endpoint |
/token |
POST | Token endpoint |
/userinfo |
GET / POST | UserInfo endpoint |
/revoke |
POST | Token revocation (RFC 7009) |
/introspect |
POST | Token introspection (RFC 7662) |
/login |
GET / POST | Login page (rendered server-side) |
/consent |
GET / POST | Consent page (rendered server-side) |
Enabled by default at /register:
| Endpoint | Method | Description |
|---|---|---|
/register |
POST | Register a new client (RFC 7591) |
/register/:client_id |
GET | Read registration (RFC 7592) |
/register/:client_id |
PUT | Update registration (RFC 7592) |
/register/:client_id |
DELETE | Delete registration |
All admin API routes are under /api/ and require a Bearer token obtained via POST /api/auth/login.
The admin portal uses session-based authentication (no token issued) to keep admin sessions out of the OIDC token store.
| Method | Path | Description |
|---|---|---|
| POST | /api/auth/login |
Admin login |
| POST | /api/auth/logout |
Admin logout |
| Method | Path | Description |
|---|---|---|
| GET | /api/users |
List users |
| POST | /api/users |
Create user |
| GET | /api/users/:id |
Get user |
| PUT | /api/users/:id |
Update user |
| DELETE | /api/users/:id |
Delete user |
| Method | Path | Description |
|---|---|---|
| GET | /api/clients |
List clients |
| POST | /api/clients |
Create client |
| GET | /api/clients/:id |
Get client |
| PUT | /api/clients/:id |
Update client |
| DELETE | /api/clients/:id |
Delete client |
| POST | /api/clients/:id/regenerate-secret |
Rotate client secret |
| Method | Path | Description |
|---|---|---|
| GET | /api/keys |
List all signing keys with cert details |
| POST | /api/settings/rotate-keys |
Rotate active key (body: {"validity_days":90}) |
| GET | /api/keys/:id/csr |
Generate PKCS#10 CSR for the key |
| POST | /api/keys/:id/import-cert |
Import CA-signed cert (body: {"cert_pem":"..."}) |
| Method | Path | Description |
|---|---|---|
| GET | /api/tokens |
List tokens (filter by subject, client, type, active) |
| DELETE | /api/tokens/:id |
Revoke token |
| Method | Path | Description |
|---|---|---|
| GET | /api/audit |
Query audit log (filter by action, actor, date range) |
| Method | Path | Description |
|---|---|---|
| GET | /api/settings |
Get server settings |
| PUT | /api/settings |
Update server settings |
Generate key pair + self-signed cert
β
βΌ
KID = x5t#S256(cert) β RFC 7517 Β§4.9
ExpiresAt = cert.NotAfter
JWKS includes x5c + x5t#S256
β
βββ (optional) Generate CSR βββΊ Submit to CA
β β
β Receive signed cert
β β
βββββββββββββββ Import Cert ββββββββββ
β
KID re-derived from new cert
ExpiresAt updated from new cert
JWKS updated automatically
β
βΌ
Rotate Key
Old key stays in JWKS until its cert expires
New key becomes active
make build-all # Build frontend then Go binary
make build # Build Go binary only (requires frontend/dist)
make build-frontend # Build React admin UI
make run # Build frontend + run server
make test # Run all Go tests
make lint # Run golangci-lint
make fmt # Run gofmt
make deps # Download Go + npm dependencies
make clean # Remove build artifacts
make install-tools # Install golangci-lint v2.5.0An interactive test client for exploring OIDC flows is included:
go run examples/test-client.go
# Visit http://localhost:9090The test client walks through:
- Dynamic client registration
- Authorization Code Flow with PKCE
- Implicit Flow
- Token refresh
- UserInfo fetch
- Token introspection
All security-relevant operations produce structured audit log entries:
| Category | Actions |
|---|---|
| User | user.login, user.login_failed, user.consent_granted, user.consent_denied |
| Token | token.issued, token.revoked |
| Client | client.registered |
| Admin | admin.login, admin.user.*, admin.client.*, admin.settings.updated, admin.keys.rotated |
Each entry records: timestamp, action, actor (type + ID), resource, status, IP address, user agent, and optional metadata.
# Build image locally
docker build -t openid-server .
# JSON-file storage (development)
docker-compose --profile json-storage up -d
# MongoDB-backed (production-like)
MONGO_USER=admin MONGO_PASSWORD=secret \
docker-compose --profile with-mongodb up -d
# Logs
docker logs openid-server -fData is persisted to the ./data volume mount.
- Zero dependencies β single file on disk (
data/openid.json) - Suitable for development and small single-instance deployments
- Not suitable for multi-instance or high-write workloads
Set MONGODB_URI to switch. Supports:
- Multi-instance deployments
- High-throughput workloads
- TTL indexes for automatic token expiry
- Passwords hashed with bcrypt (cost 10)
- PKCE enforced for public clients
- Nonce stored and checked to prevent replay attacks
auth_timepropagated through session formax_ageenforcement- Admin session uses server-side session store (not OIDC tokens)
- Signing keys backed by X.509 certificates; KIDs are RFC 7517-compliant thumbprints
- Old signing keys retained in JWKS until certificate expiry to avoid token validation gaps
For production hardening, additionally consider: TLS termination, rate limiting, MongoDB authentication, and HSM/KMS for key storage.
MIT β free for personal and commercial use.
Pull requests and issues are welcome. See docs/CONTRIBUTING.md for guidelines.