Run Your Own Identity Provider for Local Development

When integrating OAuth and OpenID Connect (OIDC) into your application, it's tempting to hard-code client IDs and secrets in your dev environment. Doing so creates a mismatch between development and production: you either end up exposing real credentials in local code or spend time wiring a cloud service into every local instance. Neither of these are good options.

Instead, a drop-in identity provider lets you run authentication flows on your machine without contacting the internet. This is the idea behind Local Identity Provider (Local IdP), an open-source project by SIOCODE. It's a minimal in-memory identity provider designed for testing OAuth, OIDC and Cognito-style login flows. Because it stores everything in memory and defines users and clients via a YAML file, it can be configured in seconds and torn down just as easily.

OIDC vs OAuth vs Login API

Modern authentication systems often blend several standards. It helps to distinguish them before diving into Local IdP:

OpenID Connect (OIDC) provider – Issues ID and access tokens (JWTs) and exposes a discovery document (/.well-known/openid-configuration) and a JSON Web Key Set (JWKS) for verifying signatures. Your application reads these keys to validate that a token was issued by the provider and to fetch metadata (issuer URL, authorization endpoint, token endpoint, etc.).

OAuth provider – Handles login and authorization. It lets users authenticate with the provider and authorizes your app to access their resources. An OAuth provider implements endpoints such as /oauth2/authorize, /oauth2/token and /userinfo.

Login API – Some providers (e.g., AWS Cognito) expose a programmatic login API. Your application collects a username and password, calls /login/init to start a challenge, then completes the challenge (e.g., an MFA code) via /login/complete. When the challenge is complete, the provider returns ID and access tokens. This pattern lets you implement your own login page while delegating credential storage and verification.

Local IdP supports all three modes. It can act as a minimal OIDC discovery server, run an OAuth2 authorization-code flow (including user consent and code exchange), and implement a Cognito-style login API with challenge/response. Everything is configurable and optional – you can enable or disable OAuth or API login via the YAML configuration.

A Single YAML File to Define Users & Clients

Unlike production identity providers that require database migrations and UI dashboards, Local IdP stores its state entirely in memory. Users, clients and settings are defined in a single YAML file, like this:

port: 8080
users:
  - id: "1"
    username: alice
    password: secret
    attributes:
      role_name: admin
      email: alice@example.com
  - id: "2"
    username: bob
    password: hunter2
    attributes:
      role_name: user
      email: bob@example.com
clients:
  - id: webapp
    audience: myapi
    secret: supersecret
    redirect_uri: http://localhost:3000/callback

This configuration sets the HTTP port, declares two users with attributes (role and email), and defines a single OAuth client with its audience, client secret and redirect URI. You can mount the file into the container or pass its path via the CONFIG_PATH environment variable.

Running Local IdP With Docker

The project ships as a multi-architecture Docker image (linux/amd64 and linux/arm64) published on Docker Hub. Pull the image and run it against your configuration:

docker pull siocode/local-idp
docker run -p 8080:8080 \
  -v $PWD/local-idp.config.yaml:/config.yaml \
  siocode/local-idp

Or add it to your docker-compose.yml next to your backend and database:

services:
  local-idp:
    image: siocode/local-idp
    ports:
      - "8080:8080"
    volumes:
      - ./local-idp.config.yaml:/config.yaml

Because everything is in memory, creating, updating or deleting users via the API does not persist across restarts. This behaviour encourages deterministic test runs: restart the container and you return to a known state.

Supported Endpoints & Flows

Local IdP exposes endpoints grouped into health/metadata, login, OAuth/OIDC and user management. Key endpoints include:

CategoryEndpoint & MethodPurpose
Health & MetadataGET /healthzHealth check (returns { "status": "OK" })
GET /.well-known/openid-configurationOIDC discovery document (issuer URL, authorization & token endpoints, JWKS URI)
GET /.well-known/jwks.jsonJSON Web Key Set used to sign JWTs
Login APIPOST /login/initStart a Cognito-style login challenge with username/password
POST /login/completeComplete the challenge (e.g., MFA code) to receive access & ID tokens
POST /login/refreshRefresh tokens using a valid refresh token
OAuth / OIDCGET /oauth2/authorizeStart the OAuth2 authorization-code flow (renders a login form)
POST /oauth2/tokenExchange an authorization code for tokens (requires client ID, secret & redirect URI)
GET /userinfoReturn user profile for a valid access token
User ManagementGET /users, GET /users/:id, PUT /users/:id, POST /users/:id/enable|disable, DELETE /users/:idManage users programmatically

Tokens are RS256-signed JWTs with Cognito-style claims (sub, aud, client_id, auth_time, token_use, iat, exp). Refresh tokens are opaque random strings. Because the keys are exposed via /.well-known/jwks.json, any OIDC-compliant client can verify tokens without needing special libraries.

Works With Existing JWT Libraries

One of the best aspects of Local IdP is that it behaves just like a real OIDC provider. Its tokens can be verified using the same libraries you already use in production. For example, the aws-jwt-verify library (a JavaScript library for verifying JWTs issued by Amazon Cognito) explicitly supports verifying JWTs from any OIDC-compatible provider. That means your backend code can swap the Cognito issuer URL for your local issuer URL and keep the verification logic unchanged.

Other compatible clients include openid-client (Node.js), OAuth2-Proxy, NextAuth.js and most OIDC/OAuth proxies. Because Local IdP exposes the standard discovery document, these clients can auto-discover endpoints and fetch signing keys without custom configuration.

Why Local IdP Matters

Authentication is a foundational piece of every application, but testing it shouldn't require cloud dependencies or expose secrets in your codebase. Local IdP brings production-like identity flows to your local machine with:

  • Zero external dependencies - No internet connection required during development
  • Instant reset - Restart the container to return to a known state
  • Complete control - Define users, clients, and flows in a single YAML file
  • Production parity - Uses the same JWT verification libraries as your production code

The image is MIT-licensed and available at github.com/SIOCODE-Open/local-idp. Whether you're building a new app with OIDC, migrating from one identity provider to another, or just want to test authentication flows without waiting on cloud services, Local IdP gives you a realistic testing environment in seconds.

What authentication flows could you test more thoroughly if you had a local identity provider that behaved exactly like production?


Have questions about Local IdP or implementing secure authentication? Contact us at info@siocode.hu.