JWKServe: A Fake JWT Authentication Service for Local Development

December 4th, 2025 990 Words

When writing backend services that validate JWT access tokens, you run into a frustrating problem: you need a real identity provider just to test your authentication logic. With Cognito, Auth0, or other OpenID Connect providers, spinning up an authentication service for local development or CI pipelines adds unnecessary complexity. You need valid signatures and correct claims, not the provider itself. That’s where JWKServe comes in.

The Annoying Reality of JWT Validation

When implementing JWT validation in your backend, you’re typically dealing with the full JWKS flow per RFC 7519 and RFC 7517. Your service needs to:

  • Verify the token contains two period separators
  • Split the token into Header.Payload.Signature values
  • Base64 decode each component
  • Use kid in the Header object to identify the needed key
  • Use iss in the Payload to fetch the Discovery Endpoint at /.well-known/openid-configuration
  • Parse the JSON structure and use jwks_uri property as the location for public keys
  • Fetch the provided JWKS Endpoint (usually at /.well-known/jwks.json)
  • Parse the JSON structure and retrieve the key with kid from the JWT Header

That’s a lot of moving parts, and for integration tests, you don’t need a real identity provider. You need tokens with valid signatures and the correct claims structure. The server itself doesn’t matter—what matters is that your validation logic works correctly with properly formatted tokens.

Flexible Testing Scenarios

JWKServe acts as a token vending machine that signs any payload as a valid JWT access token. This makes it perfect for testing complex scenarios with different custom claims, various expiration times, or edge cases around token validation. You can generate tokens on the fly for any test scenario without worrying about provider configuration or rate limits.

Installation

You can install JWKServe in two ways:

# Install jwkserve binary
cargo install jwkserve

# OR: Download jwkserve container
docker pull sbstjn/jwkserve:latest

Using the CLI: Serve Command

The serve command starts the JWKServe server. By default, it generates a new temporary RSA-2048 key on startup, which takes about 2 seconds. That’s fine for occasional use, but if you’re restarting frequently during development, instant startup helps.

# Use local binary
jwkserve serve

# Use local binary with existing key
jwkserve serve --key key.pem

The output shows what’s happening:

INFO Starting jwkserve
INFO Generating new RSA-2048 key
INFO RSA key size: 2048 bits
INFO Server listening on 0.0.0.0:3000 for issuer http://localhost:3000
INFO Supported algorithms: [RS256]

The server is now ready to generate tokens and serve JWKS endpoints. You can generate a token by posting claims to the /sign endpoint:

curl -X POST http://localhost:3000/sign \
  -H "Content-Type: application/json" \
  -d '{
    "aud": "my-app",
    "exp": 1735689600,
    "iat": 1704067200,
    "nbf": 1704067200,
    "sub": "user-12345"
  }'

The response includes a complete, valid JWT token:

{
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJteS1hcHAiLCJleHAiOjE3MzU2ODk2MDAsImlhdCI6MTcwNDA2NzIwMCwibmJmIjoxNzA0MDY3MjAwLCJzdWIiOiJ1c2VyLTEyMzQ1In0.signature_here"
}

Custom Algorithms

When serving JWKS files, you can configure RS256, RS384, and RS512 as supported algorithms:

# Use only RS256 by default
jwkserve serve

# Use RS256 and RS512
jwkserve serve --algorithm RS256 --algorithm RS512

The /sign endpoint for generating tokens supports signing algorithms as well. For flexible usage, signing is always possible using all three algorithms, regardless of what you’ve configured for the JWKS endpoint:

curl -X POST http://localhost:3000/sign/RS384 \
  -H "Content-Type: application/json" \
  -d '{
    "aud": "my-app",
    "exp": 1735689600,
    "iat": 1704067200,
    "nbf": 1704067200,
    "sub": "user-12345"
  }'

Using the CLI: Keygen Command

For faster startup during development, you can generate a key once and reuse it:

# Generate key once, also available as key size 3072 and 4096
jwkserve keygen --size 2048 --output key.pem

# Use pre-generated key (instant startup)
jwkserve serve --key key.pem

With a pre-generated key, the server starts instantly without the 2-second delay for key generation.

JWKS Flow Endpoints

To enable the full JWKS flow, JWKServe serves two endpoints you’ll need:

OpenID Discovery Endpoint

curl http://localhost:3000/.well-known/openid-configuration

The response includes the standard OpenID Connect configuration:

{
  "issuer": "http://localhost:3000",
  "authorization_endpoint": "http://localhost:3000/authorize",
  "token_endpoint": "http://localhost:3000/token",
  "jwks_uri": "http://localhost:3000/.well-known/jwks.json",
  "response_types_supported": ["id_token", "token", "code"],
  "subject_types_supported": ["public"],
  "id_token_signing_alg_values_supported": ["RS256"],
  "scopes_supported": ["openid", "profile", "email"],
  "claims_supported": ["sub", "iss", "aud", "exp", "iat", "name", "email"]
}

JWKS Key Endpoint

curl http://localhost:3000/.well-known/jwks.json

This returns the public key in JWKS format:

{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "kid": "vB_ZfJ5y5E5PPMBUyaZxoPcmKxgaclK6ImLI-YkheEs-RS256",
      "alg": "RS256",
      "n": "2x2LkXrzc2DLo7tytA0ZfBq4KWpctpe67SWL7gcfDfG7mlKXTd6Rg05Hts8i7gLPCKb-iFKpm57n...",
      "e": "AQAB"
    }
  ]
}

Your backend can now fetch this endpoint and validate tokens using the standard JWKS flow. If you’re building Rust applications, JWTiny provides a minimal validator that works seamlessly with JWKServe’s endpoints, making local development and testing straightforward.

Docker Usage

Running JWKServe in a container is straightforward:

docker run -it \
  -p 3000:3000 \
  sbstjn/jwkserve:latest

For development environments, Docker Compose makes it even easier:

services:
  jwkserve:
    image: sbstjn/jwkserve
    container_name: jwkserve
    ports:
      - "3000:3000"
    command: ["jwkserve", "serve", "--bind", "0.0.0.0"]

With this setup, you can include JWKServe in your local development stack and have it available for all your services that need JWT validation.

Testing Pipelines

In CI/CD pipelines, JWKServe shines because it eliminates the need for external dependencies. You can spin up a container, generate tokens with specific claims for your test scenarios, and tear it down when tests complete. No API keys, no rate limits, no network dependencies—just a simple service that generates valid tokens.

For example, in a GitHub Actions workflow, you might start JWKServe as a service container and use it to generate tokens for integration tests:

services:
  jwkserve:
    image: sbstjn/jwkserve:latest
    ports:
      - 3000:3000

Your tests can then generate tokens on demand and validate them against the JWKS endpoint, ensuring your authentication logic works correctly without any external dependencies.

That’s It! 🎉

JWKServe solves a real problem when writing backends that validate JWT tokens. Instead of dealing with identity provider configuration, API keys, and rate limits, you get a simple service that generates valid tokens and serves the JWKS endpoints your code needs. The server itself doesn’t matter—what matters is that your validation logic works correctly with properly formatted tokens, and JWKServe gives you exactly that.

You can find the project on GitHub and install it via cargo install jwkserve or pull the Docker image from sbstjn/jwkserve:latest. That’s it! 🎉


View on GitHub Source code is published using the MIT License.