MockServer can simulate OAuth2 authorization servers and OpenID Connect providers, enabling you to test OAuth-secured applications without connecting to a real identity provider. This is useful for:

The examples below show how to create MockServer expectations that simulate each major OAuth2 flow. All examples use the REST API to create expectations.

 

Client Credentials Flow

The client credentials flow is used for machine-to-machine communication where no user is involved. The client authenticates directly with the authorization server using its client_id and client_secret to obtain an access token.

This is the simplest OAuth2 flow and is commonly used for service-to-service API calls, background jobs, and microservice communication.

curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "POST",
        "path": "/oauth/token",
        "headers": {
            "Content-Type": ["application/x-www-form-urlencoded"]
        },
        "body": {
            "type": "PARAMETERS",
            "parameters": {
                "grant_type": ["client_credentials"],
                "client_id": ["my-client-id"],
                "client_secret": ["my-client-secret"]
            }
        }
    },
    "httpResponse": {
        "statusCode": 200,
        "headers": {
            "Content-Type": ["application/json"]
        },
        "body": {
            "type": "JSON",
            "json": "{\"access_token\": \"mock-access-token-12345\", \"token_type\": \"Bearer\", \"expires_in\": 3600, \"scope\": \"read write\"}"
        }
    }
}'
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "POST",
        "path": "/oauth/token",
        "body": {
            "type": "PARAMETERS",
            "parameters": {
                "grant_type": ["client_credentials"],
                "scope": ["read"]
            }
        }
    },
    "httpResponse": {
        "statusCode": 200,
        "headers": {
            "Content-Type": ["application/json"]
        },
        "body": {
            "type": "JSON",
            "json": "{\"access_token\": \"mock-read-only-token\", \"token_type\": \"Bearer\", \"expires_in\": 3600, \"scope\": \"read\"}"
        }
    }
}'
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "POST",
        "path": "/oauth/token",
        "headers": {
            "Authorization": ["Basic bXktY2xpZW50LWlkOm15LWNsaWVudC1zZWNyZXQ="]
        },
        "body": {
            "type": "PARAMETERS",
            "parameters": {
                "grant_type": ["client_credentials"]
            }
        }
    },
    "httpResponse": {
        "statusCode": 200,
        "headers": {
            "Content-Type": ["application/json"]
        },
        "body": {
            "type": "JSON",
            "json": "{\"access_token\": \"mock-access-token-basic\", \"token_type\": \"Bearer\", \"expires_in\": 3600}"
        }
    }
}'

Some OAuth2 clients send credentials as an HTTP Basic Authorization header (base64(client_id:client_secret)) instead of form parameters.

 

Authorization Code Flow

The authorization code flow is used by web applications and native apps to obtain tokens on behalf of a user. It involves two steps:

  1. The client redirects the user to the authorization server's /authorize endpoint, which authenticates the user and redirects back with an authorization code
  2. The client exchanges the authorization code for tokens at the /token endpoint

To mock this flow, create expectations for both the authorization endpoint and the token endpoint.

curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "GET",
        "path": "/authorize",
        "queryStringParameters": {
            "response_type": ["code"],
            "client_id": ["my-client-id"],
            "redirect_uri": ["https://myapp.example.com/callback"]
        }
    },
    "httpResponseTemplate": {
        "templateType": "JAVASCRIPT",
        "template": "return { statusCode: 302, headers: { Location: [\"https://myapp.example.com/callback?code=mock-auth-code-xyz&state=\" + (request.queryStringParameters[\"state\"] && request.queryStringParameters[\"state\"][0] ? request.queryStringParameters[\"state\"][0] : \"\")] } };"
    }
}'

The authorization endpoint redirects the user's browser back to the client application's redirect_uri with an authorization code. The state parameter is echoed back using a JavaScript response template that reads from the incoming request's query string parameters, preserving CSRF protection.

curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "POST",
        "path": "/oauth/token",
        "body": {
            "type": "PARAMETERS",
            "parameters": {
                "grant_type": ["authorization_code"],
                "code": ["mock-auth-code-xyz"],
                "redirect_uri": ["https://myapp.example.com/callback"],
                "client_id": ["my-client-id"]
            }
        }
    },
    "httpResponse": {
        "statusCode": 200,
        "headers": {
            "Content-Type": ["application/json"]
        },
        "body": {
            "type": "JSON",
            "json": "{\"access_token\": \"mock-access-token-from-code\", \"token_type\": \"Bearer\", \"expires_in\": 3600, \"refresh_token\": \"mock-refresh-token-abc\", \"scope\": \"openid profile email\"}"
        }
    }
}'

This example omits client_secret from the token request. Confidential clients (server-side web apps) must also include client_secret as a form parameter or via HTTP Basic authentication. Public clients (SPAs, mobile apps) omit it.

 

Refresh Token Flow

The refresh token flow allows clients to obtain a new access token without requiring user interaction, using a previously issued refresh token. This is used when access tokens expire and the client needs to maintain a session.

curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "POST",
        "path": "/oauth/token",
        "body": {
            "type": "PARAMETERS",
            "parameters": {
                "grant_type": ["refresh_token"],
                "refresh_token": ["mock-refresh-token-abc"],
                "client_id": ["my-client-id"]
            }
        }
    },
    "httpResponse": {
        "statusCode": 200,
        "headers": {
            "Content-Type": ["application/json"]
        },
        "body": {
            "type": "JSON",
            "json": "{\"access_token\": \"mock-new-access-token-67890\", \"token_type\": \"Bearer\", \"expires_in\": 3600, \"refresh_token\": \"mock-new-refresh-token-def\"}"
        }
    }
}'

Many authorization servers issue a new refresh token alongside the new access token (refresh token rotation). This example demonstrates that pattern.

curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "POST",
        "path": "/oauth/token",
        "body": {
            "type": "PARAMETERS",
            "parameters": {
                "grant_type": ["refresh_token"],
                "refresh_token": ["expired-refresh-token"]
            }
        }
    },
    "httpResponse": {
        "statusCode": 400,
        "headers": {
            "Content-Type": ["application/json"]
        },
        "body": {
            "type": "JSON",
            "json": "{\"error\": \"invalid_grant\", \"error_description\": \"The refresh token has expired\"}"
        }
    }
}'

Use this pattern to test how your application handles refresh token expiry — typically the user should be redirected to re-authenticate.

 

PKCE Extension

PKCE (Proof Key for Code Exchange, RFC 7636) is an extension to the authorization code flow designed for public clients (single-page apps, mobile apps) that cannot securely store a client secret. The client generates a random code_verifier and sends a derived code_challenge in the authorization request, then proves possession of the verifier when exchanging the code for tokens.

curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "GET",
        "path": "/authorize",
        "queryStringParameters": {
            "response_type": ["code"],
            "client_id": ["my-spa-client"],
            "redirect_uri": ["https://myapp.example.com/callback"],
            "code_challenge_method": ["S256"],
            "code_challenge": [".+"]
        }
    },
    "httpResponseTemplate": {
        "templateType": "JAVASCRIPT",
        "template": "return { statusCode: 302, headers: { Location: [\"https://myapp.example.com/callback?code=mock-pkce-auth-code&state=\" + (request.queryStringParameters[\"state\"] && request.queryStringParameters[\"state\"][0] ? request.queryStringParameters[\"state\"][0] : \"\")] } };"
    }
}'

The code_challenge parameter is matched with a regex (.+) since it will be a different Base64url-encoded hash each time. The state parameter is echoed back using a JavaScript response template. In a real flow, the authorization server validates the challenge against the verifier during token exchange.

curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "POST",
        "path": "/oauth/token",
        "body": {
            "type": "PARAMETERS",
            "parameters": {
                "grant_type": ["authorization_code"],
                "code": ["mock-pkce-auth-code"],
                "redirect_uri": ["https://myapp.example.com/callback"],
                "client_id": ["my-spa-client"],
                "code_verifier": [".+"]
            }
        }
    },
    "httpResponse": {
        "statusCode": 200,
        "headers": {
            "Content-Type": ["application/json"]
        },
        "body": {
            "type": "JSON",
            "json": "{\"access_token\": \"mock-pkce-access-token\", \"token_type\": \"Bearer\", \"expires_in\": 3600, \"id_token\": \"mock-id-token-jwt\", \"scope\": \"openid profile\"}"
        }
    }
}'

The code_verifier is matched with a regex since MockServer does not need to verify the PKCE proof — it just needs to accept the token exchange request. Note that PKCE flows for public clients do not include a client_secret.

 

Dynamic Token Responses with Response Templates

The examples above return static token values. For more realistic testing — especially when running multiple tests in parallel — you can use response templates to generate unique tokens for each request.

curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "POST",
        "path": "/oauth/token",
        "body": {
            "type": "PARAMETERS",
            "parameters": {
                "grant_type": ["client_credentials"]
            }
        }
    },
    "httpResponseTemplate": {
        "templateType": "MUSTACHE",
        "template": "{\"statusCode\": 200, \"headers\": {\"Content-Type\": [\"application/json\"]}, \"body\": \"{\\\"access_token\\\": \\\"{{uuid}}\\\", \\\"token_type\\\": \\\"Bearer\\\", \\\"expires_in\\\": 3600, \\\"scope\\\": \\\"read write\\\"}\"}"
    }
}'

Each request receives a unique access_token generated by the {{uuid}} built-in template variable.

curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "POST",
        "path": "/oauth/token",
        "body": {
            "type": "PARAMETERS",
            "parameters": {
                "grant_type": ["client_credentials"]
            }
        }
    },
    "httpResponseTemplate": {
        "templateType": "JAVASCRIPT",
        "template": "return { statusCode: 200, headers: { \"Content-Type\": [\"application/json\"] }, body: JSON.stringify({ access_token: uuid, token_type: \"Bearer\", expires_in: 3600, issued_at: parseInt(now_epoch), scope: \"read write\" }) };"
    }
}'

JavaScript templates provide access to built-in template variables like uuid and now_epoch for generating dynamic values. The issued_at field contains the epoch time when the token was issued.

 

Matching Protected Resources by Bearer Token

After obtaining an access token, clients include it in the Authorization header when calling protected APIs. You can mock these protected endpoints to accept any bearer token, require a specific token, or reject missing/invalid tokens.

curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "GET",
        "path": "/api/resource",
        "headers": {
            "Authorization": ["Bearer .+"]
        }
    },
    "httpResponse": {
        "statusCode": 200,
        "headers": {
            "Content-Type": ["application/json"]
        },
        "body": {
            "type": "JSON",
            "json": "{\"id\": 1, \"name\": \"Protected Resource\", \"description\": \"This resource requires authentication\"}"
        }
    }
}'

The regex Bearer .+ matches any non-empty bearer token value.

curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "GET",
        "path": "/api/resource",
        "headers": {
            "Authorization": ["Bearer mock-access-token-12345"]
        }
    },
    "httpResponse": {
        "statusCode": 200,
        "headers": {
            "Content-Type": ["application/json"]
        },
        "body": {
            "type": "JSON",
            "json": "{\"id\": 1, \"name\": \"Protected Resource\"}"
        }
    }
}'

Match a specific token to ensure your application sends the correct token obtained from the mock token endpoint.

curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "GET",
        "path": "/api/resource",
        "headers": {
            "!Authorization": ["Bearer .+"]
        }
    },
    "httpResponse": {
        "statusCode": 401,
        "headers": {
            "Content-Type": ["application/json"],
            "WWW-Authenticate": ["Bearer realm=\"mockserver\", error=\"invalid_token\""]
        },
        "body": {
            "type": "JSON",
            "json": "{\"error\": \"unauthorized\", \"error_description\": \"Access token is missing or invalid\"}"
        }
    }
}'

The ! prefix on the header name creates a negative matcher — this expectation matches requests that do not have a valid Bearer token. Use this with a lower priority to create a catch-all 401 response alongside a higher-priority expectation that matches valid tokens.

curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "DELETE",
        "path": "/api/admin/resource",
        "headers": {
            "Authorization": ["Bearer mock-read-only-token"]
        }
    },
    "httpResponse": {
        "statusCode": 403,
        "headers": {
            "Content-Type": ["application/json"],
            "WWW-Authenticate": ["Bearer realm=\"mockserver\", error=\"insufficient_scope\", scope=\"admin\""]
        },
        "body": {
            "type": "JSON",
            "json": "{\"error\": \"forbidden\", \"error_description\": \"Insufficient scope for this resource\"}"
        }
    }
}'

Simulate a scenario where the access token is valid but does not have the required scope for the requested operation.

 

OpenID Connect Discovery

OpenID Connect (OIDC) clients typically start by fetching the provider's discovery document from /.well-known/openid-configuration to learn the authorization, token, and JWKS endpoint URLs. Many OAuth2 client libraries require this endpoint to be available during initialization.

curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "GET",
        "path": "/.well-known/openid-configuration"
    },
    "httpResponse": {
        "statusCode": 200,
        "headers": {
            "Content-Type": ["application/json"]
        },
        "body": {
            "type": "JSON",
            "json": "{\"issuer\": \"http://localhost:1080\", \"authorization_endpoint\": \"http://localhost:1080/authorize\", \"token_endpoint\": \"http://localhost:1080/oauth/token\", \"userinfo_endpoint\": \"http://localhost:1080/userinfo\", \"jwks_uri\": \"http://localhost:1080/.well-known/jwks.json\", \"response_types_supported\": [\"code\", \"token\", \"id_token\"], \"subject_types_supported\": [\"public\"], \"id_token_signing_alg_values_supported\": [\"RS256\"], \"scopes_supported\": [\"openid\", \"profile\", \"email\"], \"token_endpoint_auth_methods_supported\": [\"client_secret_basic\", \"client_secret_post\"], \"grant_types_supported\": [\"authorization_code\", \"client_credentials\", \"refresh_token\"]}"
        }
    }
}'

The discovery document tells OIDC clients where to find all the other endpoints. All URLs point to MockServer (localhost:1080) so the client library interacts entirely with the mock.

curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "GET",
        "path": "/.well-known/jwks.json"
    },
    "httpResponse": {
        "statusCode": 200,
        "headers": {
            "Content-Type": ["application/json"]
        },
        "body": {
            "type": "JSON",
            "json": "{\"keys\": [{\"kty\": \"RSA\", \"use\": \"sig\", \"kid\": \"mock-key-1\", \"alg\": \"RS256\", \"n\": \"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw\", \"e\": \"AQAB\"}]}"
        }
    }
}'

The JWKS endpoint exposes public keys that OIDC clients use to verify the signature of ID tokens. For testing purposes you can use a placeholder RSA key — unless your application actually validates JWT signatures, in which case you should generate a real key pair and sign your mock ID tokens with the corresponding private key.

curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "GET",
        "path": "/userinfo",
        "headers": {
            "Authorization": ["Bearer .+"]
        }
    },
    "httpResponse": {
        "statusCode": 200,
        "headers": {
            "Content-Type": ["application/json"]
        },
        "body": {
            "type": "JSON",
            "json": "{\"sub\": \"mock-user-123\", \"name\": \"Test User\", \"email\": \"test@example.com\", \"email_verified\": true, \"picture\": \"https://example.com/photo.jpg\"}"
        }
    }
}'

The UserInfo endpoint returns claims about the authenticated user. This is called by OIDC clients after obtaining an access token.

 

Turnkey OIDC Mock Identity Provider

The examples above show how to build individual OIDC/OAuth2 expectations by hand. For cases where you need a complete identity provider in one call, MockServer provides a turnkey OIDC mock via PUT /mockserver/oidc. A single request generates all six standard endpoints with real, verifiable tokens.

This is especially useful when your application's OIDC client library requires all endpoints to be present and tokens to be cryptographically valid — which is common with libraries like jose, Nimbus JOSE, and Spring Security.

How it works

  1. MockServer generates a fresh RSA 2048-bit key pair on each call
  2. It creates expectations for six endpoints: discovery, JWKS, token, userinfo, introspection, and revocation
  3. The token endpoint returns real RSA-signed JWTs (both access_token and id_token) that can be validated end-to-end against the JWKS endpoint
  4. The discovery endpoint at /.well-known/openid-configuration points all URLs to MockServer, so your OIDC client library works without modification

Generated endpoints

Endpoint Method Default path Description
Discovery GET /.well-known/openid-configuration OpenID Connect discovery document with all endpoint URLs
JWKS GET /.well-known/jwks.json JSON Web Key Set containing the public key for token verification
Token POST /token Returns RSA-signed JWTs (access_token + id_token), accepts any grant type
UserInfo GET /userinfo Returns user claims (subject, plus any additional claims)
Introspection POST /introspect Token introspection (RFC 7662); reports active: true unless issueExpiredToken is set
Revocation POST /revoke Token revocation (RFC 7009); always returns 200 OK

Configuration fields

All fields are optional. With an empty request body ({}), MockServer uses sensible defaults.

Field Type Default Description
issuer string http://localhost:1080 The iss claim in tokens and the base URL for discovery
subject string mock-user The sub claim in tokens
clientId string mock-client Client identifier (included in tokens as context)
audience string mock-audience The aud claim in tokens
scopes array of strings ["openid", "profile", "email"] Scopes included in tokens and the discovery document
tokenExpirySeconds int 3600 Token lifetime in seconds
additionalClaims object {} Extra claims to include in tokens and the userinfo response
jwksPath string /.well-known/jwks.json Path for the JWKS endpoint
tokenPath string /token Path for the token endpoint
authorizePath string /authorize Path for the authorization endpoint (advertised in discovery)
userinfoPath string /userinfo Path for the userinfo endpoint
introspectPath string /introspect Path for the introspection endpoint
revokePath string /revoke Path for the revocation endpoint

Negative-testing flags

These flags intentionally break the generated tokens in specific ways, so you can test how your application handles invalid tokens.

Flag Type Default Effect
issueExpiredToken boolean false Sets the token exp claim to one hour in the past, so the token is already expired when issued. The introspection endpoint also reports active: false.
wrongIssuer boolean false Appends /wrong to the iss claim in the token, making it differ from the issuer in the discovery document. OIDC clients that validate the issuer will reject the token.
tamperedSignature boolean false Modifies the JWT signature after signing, so signature verification against the JWKS public key will fail.
curl -v -X PUT "http://localhost:1080/mockserver/oidc" \
  -H "Content-Type: application/json" \
  -d '{}'

MockServer creates six expectations (discovery, JWKS, token, userinfo, introspect, revoke) with default paths and claims. The token endpoint immediately returns valid RSA-signed JWTs.

curl -v -X PUT "http://localhost:1080/mockserver/oidc" \
  -H "Content-Type: application/json" \
  -d '{
    "issuer": "http://localhost:1080",
    "subject": "test-user-42",
    "audience": "my-api",
    "scopes": ["openid", "profile", "admin"],
    "tokenExpirySeconds": 7200,
    "additionalClaims": {
      "email": "test@example.com",
      "name": "Test User",
      "role": "admin"
    }
  }'
curl -v -X PUT "http://localhost:1080/mockserver/oidc" \
  -H "Content-Type: application/json" \
  -d '{
    "issueExpiredToken": true
  }'

Tokens returned by the /token endpoint have an exp claim in the past. Use this to test that your application correctly handles token expiry.

curl -v -X PUT "http://localhost:1080/mockserver/oidc" \
  -H "Content-Type: application/json" \
  -d '{
    "tamperedSignature": true
  }'

Tokens have a corrupted signature that will not verify against the JWKS public key. Use this to test that your application rejects tokens with invalid signatures.

curl -v -X PUT "http://localhost:1080/mockserver/oidc" \
  -H "Content-Type: application/json" \
  -d '{
    "wrongIssuer": true
  }'

The iss claim in the token will differ from the issuer advertised in the discovery document. Use this to test issuer validation in your OIDC client.

 

Error Responses

Testing error scenarios is critical for building resilient OAuth2 integrations. The OAuth2 specification defines standard error codes that authorization servers return when requests fail.

curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "POST",
        "path": "/oauth/token",
        "body": {
            "type": "PARAMETERS",
            "parameters": {
                "grant_type": ["client_credentials"],
                "client_id": ["invalid-client"]
            }
        }
    },
    "httpResponse": {
        "statusCode": 401,
        "headers": {
            "Content-Type": ["application/json"]
        },
        "body": {
            "type": "JSON",
            "json": "{\"error\": \"invalid_client\", \"error_description\": \"Client authentication failed\"}"
        }
    }
}'
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "POST",
        "path": "/oauth/token",
        "body": {
            "type": "PARAMETERS",
            "parameters": {
                "grant_type": ["authorization_code"],
                "code": ["expired-or-used-code"]
            }
        }
    },
    "httpResponse": {
        "statusCode": 400,
        "headers": {
            "Content-Type": ["application/json"]
        },
        "body": {
            "type": "JSON",
            "json": "{\"error\": \"invalid_grant\", \"error_description\": \"The authorization code has expired or has already been used\"}"
        }
    }
}'
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
    "httpRequest": {
        "method": "POST",
        "path": "/oauth/token",
        "body": {
            "type": "PARAMETERS",
            "parameters": {
                "grant_type": ["password"]
            }
        }
    },
    "httpResponse": {
        "statusCode": 400,
        "headers": {
            "Content-Type": ["application/json"]
        },
        "body": {
            "type": "JSON",
            "json": "{\"error\": \"unsupported_grant_type\", \"error_description\": \"The authorization grant type is not supported\"}"
        }
    }
}'