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.
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.
The authorization code flow is used by web applications and native apps to obtain tokens on behalf of a user. It involves two steps:
/authorize endpoint, which authenticates the user and redirects back with an authorization code/token endpointTo 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.
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 (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.
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.
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 (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.
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.
| 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 |
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 |
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.
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\"}"
}
}
}'