--- title: API Security description: Secure the MockServer control plane with mTLS client certificate authentication, JWT bearer tokens, or both, plus network and CORS hardening tips. layout: page pageOrder: 2 section: 'Security' subsection: true sitemap: priority: 0.7 changefreq: 'monthly' lastmod: 2026-05-30T08:00:00+00:00 ---
Multiple techniques can be used to lock down MockServer deployments, as follows:
Proxy Authentication: When MockServer is used as an HTTP proxy, it supports HTTP Basic proxy authentication (RFC 7235) on the data plane via three configuration properties: proxyAuthenticationUsername, proxyAuthenticationPassword, and proxyAuthenticationRealm. When these are configured, MockServer responds with 407 Proxy Authentication Required and a Proxy-Authenticate: Basic realm="…" header to any CONNECT or forwarded request that does not include valid Proxy-Authorization credentials. The authentication mechanisms described below secure the control plane (expectation management, verification, retrieval, etc.), which is separate from this data-plane proxy authentication.
Common confusion: The MockServerClient.withProxyConfiguration() method configures how the client connects to MockServer through an upstream proxy—it does not add authentication to MockServer itself. See the client documentation for details.
Authentication can be enabled for all control plane requests (i.e. create expectations, clear, reset, verify, retrieve, stop, etc) using either mTLS, JWT or both.
If both mTLS and JWT are enabled mTLS will be validated first.
Control plane authentication settings are read live, not frozen at startup. The CA chain, JWK source, issuer and audience used to authenticate control plane requests are re-read from the current configuration on every request, so enabling, disabling or re-pointing control plane authentication on a running MockServer takes effect immediately — whether you change it with a system property, a Configuration setter, or PUT /mockserver/configuration.
This is what makes it possible to lock down an instance that is already running. It also means the trust anchor you set at startup is not permanent: anything that can change MockServer's configuration can change who is trusted. Two things follow. First, if you run MockServer embedded, set the CA chain on the Configuration instance you start the server with rather than through the global static properties, so unrelated code in the same JVM (including other tests) cannot move it. Second, if the control plane is reachable by anyone you would not trust to change its own trust anchor, keep control plane authentication enabled — PUT /mockserver/configuration is itself a control plane request and is refused when authentication is enabled and not satisfied.
When mTLS authentication is enabled all control plane requests need to be received over a mTLS connection where the client's X509 certificates can be validated using the controlPlaneTLSMutualAuthenticationCAChain
{% include_subpage _includes/control_plane_authentication_mtls_configuration.html %}When JWT authentication is enabled all control plane requests need and JWT via a authorization header which is validated using the controlPlaneJWTAuthenticationJWKSource
{% include_subpage _includes/control_plane_authentication_jwt_configuration.html %}When OIDC authentication is enabled all control plane requests need a Bearer access token via an authorization header issued by an external OpenID Connect identity provider. The token signature is verified against the provider's JWK set (configured directly via controlPlaneOidcJwksUri or discovered from controlPlaneOidcIssuer), and its issuer, audience, expiry and required scopes are checked. The verified subject is recorded as the principal in the control plane audit log.
{% include_subpage _includes/control_plane_authentication_oidc_configuration.html %}Once a principal has been authenticated, you can layer a coarse role-based authorization check on top. Authorization maps the verified principal's OIDC scopes or groups to one of three hierarchical roles and enforces them on every control-plane operation.
Prerequisite: Authorization requires a verified principal, so OIDC authentication must be configured. Without authentication there is no principal and authorization has nothing to enforce.
Important limitations:
/mockserver/status and /mockserver/ready are neither authenticated nor authorized — they are always reachable so health-check infrastructure never needs credentials. (/mockserver/bind and /mockserver/stop are authenticated when control-plane authentication is enabled.)/mockserver/dashboard) and its live UI WebSocket require the same control-plane authentication as every other control-plane endpoint when it is enabled — otherwise anyone with network reach could read all captured traffic. The dashboard is a read, so a read-only role may view it. When no control-plane authentication is configured (the default) the dashboard stays open. Because a browser cannot attach a bearer token to a WebSocket, a token/OIDC-authenticated dashboard must be served through an authenticating reverse proxy (or use mutual TLS)./mockserver/mcp) is authenticated (the bearer token is validated), but per-tool authorization is not yet enforced. Treat any authenticated MCP session as mutate-capable./mockserver/metrics is intentionally NOT behind control-plane authentication — Prometheus and OpenTelemetry scrapers cannot present a control-plane certificate or bearer token while scraping, so gating it would break metrics collection. See Securing the metrics scrape endpoint below for what it can expose and how to lock it down. (Note the JSON metrics snapshot PUT /mockserver/retrieve?type=METRICS is behind the control-plane auth gate — only the Prometheus scrape endpoint is open.)The three roles form a strict hierarchy. A principal granted a higher role satisfies every requirement at or below it:
| Role | Permits | Examples |
|---|---|---|
| read | Read-only control-plane operations | retrieve, verify, verifySequence, explainUnmatched, debugMismatch |
| mutate | Everything read permits, plus all mutating operations | expectation, clear, reset, configuration, chaosExperiment, LLM endpoints |
| admin | Everything mutate permits (reserved for future fine-grained admin operations) | Currently identical to mutate |
Enable authorization with controlPlaneAuthorizationEnabled (default false) and map scope/group values to roles with controlPlaneScopeMapping:
| Property | Environment Variable | Default | Description |
|---|---|---|---|
mockserver.controlPlaneAuthorizationEnabled |
MOCKSERVER_CONTROL_PLANE_AUTHORIZATION_ENABLED |
false |
Enable coarse role-based authorization of all control-plane requests. Requires a verified principal (OIDC authentication must be configured). |
mockserver.controlPlaneScopeMapping |
MOCKSERVER_CONTROL_PLANE_SCOPE_MAPPING |
empty | Comma-separated scope=role pairs mapping a principal's OIDC scope or group value to a role (read, mutate, or admin). Example: platform-admins=admin,qa-team=mutate,viewers=read |
Example — system property configuration:
-Dmockserver.controlPlaneAuthorizationEnabled=true
-Dmockserver.controlPlaneScopeMapping=platform-admins=admin,qa-team=mutate,viewers=read
Example — Java programmatic configuration:
ConfigurationProperties.controlPlaneAuthorizationEnabled(true);
ConfigurationProperties.controlPlaneScopeMapping(Map.of(
"platform-admins", ControlPlaneRole.ADMIN,
"qa-team", ControlPlaneRole.MUTATE,
"viewers", ControlPlaneRole.READ
));
controlPlaneScopeMapping is denied every mutation (and every read unless a read-or-higher role is mapped). An unmapped or blank role value grants nothing.Forbidden for control plane message. The detailed reason (granted role vs. required role) is logged server-side only so authorization policy is not disclosed to the client.outcome=FORBIDDEN. Successful authorized requests are recorded with outcome=AUTHORIZED.viewers and qa-team scopes is granted mutate from the example mapping above).For the most security-focused deployment, set the configuration properties below to the values shown. Each can be set as a Java system property (-Dmockserver.<name>=<value>), an environment variable (MOCKSERVER_<NAME>), or in a properties file — see Configuration Properties for the exact syntax and full description of each.
mockserver.localBoundIP=127.0.0.1 — bind the listener to the loopback interface so MockServer is reachable only from the local host. When running inside a container, set this to 0.0.0.0 and restrict access at the container, network, or orchestration layer instead.mockserver.attemptToProxyIfNoMatchingExpectation=false — return 404 Not Found for any request that matches no expectation, so MockServer serves only the traffic you have explicitly defined and does not forward unmatched requests upstream.The Prometheus scrape endpoint GET /mockserver/metrics is served without control-plane authentication by design: Prometheus and OpenTelemetry scrapers cannot attach a control-plane client certificate or bearer token to a scrape, so requiring one would prevent metrics from being collected at all. (In contrast, the JSON metrics snapshot PUT /mockserver/retrieve?type=METRICS goes through HttpState.handle and is gated by control-plane authentication like every other retrieve operation.)
Because the endpoint is open, be aware of what its labels can reveal to anyone with network reach:
upstream_host label (host only, never the full URL or path), exposing which backends MockServer forwards to.mock_server_llm_input_tokens, mock_server_llm_output_tokens, and mock_server_llm_cost_usd are labelled by provider and model.To secure it, choose whichever fits your deployment (they combine):
mockserver.metricsEnabled=false (the default) fully disables the endpoint: /mockserver/metrics returns 404 Not Found and no metrics are exposed. Leave it off unless you are actively scraping.mockserver.localBoundIP=127.0.0.1), keep it on an internal network, or restrict it with a firewall rule or Kubernetes NetworkPolicy that permits only your Prometheus scraper.mockserver.otelMetricsEnabled) and Prometheus Remote-Write (mockserver.prometheusRemoteWriteEnabled) push the same metrics to your backend and expose no scrape endpoint at all. With metrics pushed and metricsEnabled=false, there is nothing to lock down on the MockServer side.mockserver.forwardProxyBlockPrivateNetworks=true — reject forward and proxy targets that resolve to loopback, link-local, RFC 1918 private, or cloud-metadata addresses (e.g. 169.254.169.254), keeping forwarded requests off internal networks (SSRF protection).mockserver.forwardProxyTLSX509CertificatesTrustManagerType=JVM — validate upstream HTTPS certificates against the JDK trust store, exactly as a standard Java HTTPS client would. To trust a specific private CA, use =CUSTOM together with mockserver.forwardProxyTLSCustomTrustX509Certificates pointing at your CA bundle.mockserver.tlsAllowInsecureProtocols=false — negotiate TLSv1.2 and above only, excluding the deprecated TLSv1 and TLSv1.1 protocols. Add TLSv1.3 to mockserver.tlsProtocols to require the most modern protocol.If you use response templates, note that templates cannot reach Java classes by default, so this hardening is already in place and nothing needs configuring (templates you do not use carry no risk):
mockserver.velocityDisallowClassLoading defaults to true — Velocity templates run with a secure uberspector so they cannot load arbitrary Java classes. Setting it to false re-opens that, which on an exposed control plane is a remote-code-execution path.mockserver.javascriptAllowedClasses defaults to empty, meaning JavaScript templates resolve NO Java class via Java.type(...). Only list classes a template genuinely needs, and avoid the * wildcard (fully unrestricted) on any instance untrusted callers can reach.Bound the size of inbound request lines, headers, and chunks so a single client cannot exhaust memory with an oversized request:
mockserver.maxInitialLineLength=8192 — cap the request line (method + URI + version) at 8 KiB.mockserver.maxHeaderSize=16384 — cap the combined request headers at 16 KiB.mockserver.maxChunkSize=16384 — cap a single chunked-transfer-encoding chunk at 16 KiB.The body-size limits mockserver.maxRequestBodySize and mockserver.maxResponseBodySize provide complementary bounds on payload size.
mockserver.corsAllowOrigin — set this to the specific origin(s) you trust rather than allowing any origin, and keep mockserver.corsAllowCredentials=false unless credentialed cross-origin requests are genuinely required. See CORS.