Status: Implemented (v0.2 complete, file-based storage)
Owner: Wei Labs / tendant
Last updated: 2026-02-03
simple-idp is a standalone Identity Provider (IdP) that offers:
- First-party user authentication (local users)
- OAuth 2.0 + OpenID Connect (OIDC) provider endpoints
- Token issuance + verification primitives (JWKS, key rotation)
- Minimal, dependable operational footprint (Go + Postgres, optional Redis later)
simple-idp is explicitly not dependent on simple-idm at runtime or build time.
- Provide a self-hostable, production-oriented OIDC issuer for internal tools and early SaaS.
- Support Authorization Code + PKCE as the primary flow.
- Provide stable subject identifiers:
subis immutable for a given user. - Keep the architecture modular and easy to extend (connectors, MFA, SCIM later).
- Small, understandable codebase.
- Stateless app servers; persistent state in Postgres.
- Clear migration path from existing
simple-idmauth logic (if desired). - Strong security defaults (Argon2, secure cookies, strict redirect URI validation).
- Social login connectors (Google/GitHub/etc.) — later.
- Enterprise features (SAML, advanced MFA policies, device posture) — later.
- Full admin console with complex org/tenant modeling — keep IdP tenant-agnostic in v1.
- Fine-grained authorization model (RBAC/ABAC) — belongs to apps (e.g.,
simple-idm).
-
Auth UI & Session Layer
/login,/logout- Secure session cookies for browser login (used by
/authorize)
-
OIDC Provider
- Discovery:
/.well-known/openid-configuration - Authorization:
/authorize(auth code + PKCE) - Token:
/token - UserInfo:
/userinfo(optional but recommended) - JWKS:
/.well-known/jwks.jsonor/jwks.json
- Discovery:
-
Core Stores (Postgres)
- Users and credentials (Argon2)
- OAuth clients
- Authorization codes
- Tokens (refresh + access) and revocation
- Consents (optional; can start “trusted clients” only)
- Signing keys + rotation metadata
- Audit events (lightweight; v1 optional)
-
Crypto & Key Management
- RSA or Ed25519 signing keys (recommend Ed25519 for simplicity/perf)
- Key rotation strategy and JWKS publication
- App redirects user to:
GET /authorize?client_id=...&redirect_uri=...&response_type=code&scope=openid...&code_challenge=...
simple-idpchecks session:- If not logged in → redirect to
/login
- If not logged in → redirect to
- After login, IdP creates
auth_coderecord and redirects back:302 Location: {redirect_uri}?code=...&state=...
- App exchanges code:
POST /tokenwithcode_verifier
- IdP returns tokens:
id_token(JWT),access_token(JWT or opaque), optionalrefresh_token
- App verifies
id_tokenvia JWKS.
Recommended layout:
simple-idp/
cmd/idp/
main.go
internal/
config/ # config loading, validation
http/ # router + middleware
auth/ # login/session, cookie, csrf
oidc/ # authorize/token/userinfo flows
crypto/ # jwks, key rotation, jwt signing
store/ # persistence interfaces + sql implementation
domain/ # types: User, Client, Consent, Token, etc.
audit/ # optional
migrations/
docs/
Dockerfile
docker-compose.yml
go.mod
Rule: all production code under internal/ to avoid accidental coupling.
GET /.well-known/openid-configurationGET /authorizePOST /tokenGET /userinfo(optional in v1, recommended)GET /.well-known/jwks.json(or/jwks.json)POST /revoke(optional v1)POST /introspect(optional v1; only if using opaque tokens)GET /logout(basic RP-initiated logout optional)
GET /loginPOST /loginPOST /logout(preferred over GET in production)
GET /healthzGET /readyzGET /metrics(optional; Prometheus)
- Always JWT signed by IdP key.
- Contains:
iss,sub,aud,exp,iatnonce(if provided)email,email_verified(if scope allows)name,preferred_username(if profile scope allows)
Choose one for v1:
Option A (simplest): JWT access token
- Pros: no introspection required; stateless validation for resource servers.
- Cons: revocation is hard; shorter expiry recommended.
Option B: Opaque access token + introspection
- Pros: revocable; better control.
- Cons: requires introspection endpoint and server-side checks.
Recommendation: v1 use JWT access tokens with short TTL (e.g., 10–15 min) + refresh tokens.
- Stored server-side (hashed) with rotation (recommended).
- Offline session model:
- user_id + client_id + scopes
- Support revocation.
Below is a suggested baseline schema (snake_case). Adjust to your conventions.
id(uuid, pk)email(text, unique)email_verified(bool, default false)name(text, nullable)preferred_username(text, nullable, unique optional)created_at,updated_atdisabled_at(timestamp, nullable)
id(uuid, pk)user_id(uuid, fk users)type(text) —passwordin v1password_hash(text) — Argon2id hash stringcreated_at,updated_atdisabled_at(timestamp, nullable)
id(uuid, pk)user_id(uuid, fk)session_token_hash(text, unique) — hash of cookie tokencreated_at,expires_atlast_seen_atrevoked_at(timestamp, nullable)
id(text, pk) — client_idname(text)type(text) —public|confidentialclient_secret_hash(text, nullable)redirect_uris(text[]) — validatedallowed_scopes(text[])grant_types(text[]) — includeauthorization_code,refresh_tokenresponse_types(text[]) — includecodecreated_at,updated_atdisabled_at(timestamp, nullable)
id(uuid, pk)user_id(uuid)client_id(text)scopes(text[])created_at,updated_at- unique(user_id, client_id)
id(uuid, pk)code_hash(text, unique)user_id(uuid)client_id(text)redirect_uri(text)scopes(text[])nonce(text, nullable)code_challenge(text)code_challenge_method(text) —S256created_at,expires_atconsumed_at(timestamp, nullable)
id(uuid, pk)user_id(uuid)client_id(text)kind(text) —refresh|accesstoken_hash(text, unique)scopes(text[])created_at,expires_atrevoked_at(timestamp, nullable)rotated_from_token_id(uuid, nullable)
id(uuid, pk)kid(text, unique)alg(text) —EdDSAorRS256public_jwk(jsonb)private_key_enc(bytea) — encrypted at rest (envelope) or KMS refcreated_atnot_before(timestamp, nullable)expires_at(timestamp, nullable)rotated_at(timestamp, nullable)
id(uuid, pk)type(text) —login_success,login_failure,token_issued, etc.actor_user_id(uuid, nullable)client_id(text, nullable)ip(inet, nullable)user_agent(text, nullable)created_atdata(jsonb, nullable)
Config sources:
- environment variables (default)
- optional YAML file for local dev
Key config:
IDP_ISSUER_URL(e.g., https://auth.example.com)IDP_HTTP_ADDR(e.g., :8080)IDP_PUBLIC_BASE_URL(if behind proxy)IDP_DB_DSNIDP_COOKIE_SECRET(32+ bytes)IDP_SESSION_TTLIDP_ID_TOKEN_TTLIDP_ACCESS_TOKEN_TTLIDP_REFRESH_TOKEN_TTLIDP_ALLOWED_ORIGINS(CORS, if needed)IDP_TRUSTED_PROXIES
Client bootstrap options:
- v1: static clients in config
- v1.1: DB-managed clients with admin endpoint
- Password hashing: Argon2id with sensible parameters.
- Session cookies:
HttpOnly,Secure,SameSite=Lax(orNoneif cross-site required)- rotate session IDs on login
- CSRF protection for login form.
- Redirect URI validation:
- exact match against registered
redirect_uris - reject wildcards by default
- exact match against registered
- PKCE required for public clients.
- Token signing key rotation:
- keep old keys in JWKS until all tokens expire
- Rate limit login attempts (per IP + per account) — can be in-memory first.
- Audit logs for auth events.
- Stateless
simple-idppods - Postgres (CloudNativePG or managed)
- Ingress terminates TLS; forward
X-Forwarded-*headers.
- Deployment + HPA
- Service
- Ingress
- Secret (cookie secret, DB creds)
- ConfigMap (issuer url, token TTL)
- PodDisruptionBudget (optional)
Apps should treat simple-idp as the identity source.
Identity key: (issuer, sub)
Apps typically:
- Redirect to
/authorize - Exchange code at
/token - Verify
id_tokenusing JWKS - Map
(issuer, sub)to an internal account row - Manage authorization (roles/tenants) locally
Two strategies:
- Stand up
simple-idpwith new DB - Apps re-login; new
subvalues are generated - Simplest operationally
- Export
usersandpassword_hashesfromsimple-idm - Import into
simple-idpusers+user_credentials - Ensure
sub= existing stable user ID (recommended) - Keep email verification flags
- Redirect URI validation
- PKCE verification
- Token claim correctness
- Session cookie lifecycle
- Full auth code + PKCE flow using a test RP
- JWKS fetch + token verification
- Refresh token rotation and revocation
- Use a simple OIDC client library to validate standard compliance.
- Local users (email + password)
- Auth code + PKCE
- JWT id_token + access token
- JWKS publication
- Static client config (env vars)
- Minimal login UI
- Refresh tokens + rotation
/userinfoendpoint- File-based JSON storage
- PostgreSQL storage backend
- Password reset + email verification
- Consent UI (optional)
- Rate limiting improvements
- Hardening, docs, k8s manifests, upgrade guide
Issuer: https://auth.example.com
/.well-known/openid-configurationauthorization_endpoint:https://auth.example.com/authorizetoken_endpoint:https://auth.example.com/tokenjwks_uri:https://auth.example.com/.well-known/jwks.jsonuserinfo_endpoint:https://auth.example.com/userinfo