{
"host": "api.example.com",
"path": "/v1/*",
"backend": "http://api:8080",
"auth": {
"provider": "company-api",
"when": { "method": ["GET", "POST"] },
"identity": {
"scopes": ["read:things"],
"roles": ["user"]
}
}
}Protect APIs and MCP tools
Useful if you want to…
expose an API only to authenticated users, with roles, scopes or claim conditions per route;
accept machine clients with a bearer JWT, validated locally against a JWKS;
expose MCP tools with the OAuth protected-resource metadata clients expect (resource/audience, scope, tool list);
keep the browser session and the machine token planes separate.
Two planes
Browser session. Gate One hosts (or delegates) the login page, sets
an RP session cookie and proxies the request after authentication. Use
type: "oidc" (or http-bridge for auth-bridge).
Machine bearer. A client presents Authorization: Bearer <JWT>.
Gate One validates the signature through the provider’s JWKS, then the
audience and the identity conditions. Use type: "jwt"; this provider
never hosts a login page and never issues a token.
Route rules
auth.when decides when authentication is required from technical
conditions (method, query, header, body). auth.identity decides who
is allowed after a successful authentication:
An empty auth ({}) means authentication is always required.
A claim condition accepts one value or a list: the listed values are
OR-ed (anyOf) on the same claim, while distinct keys still combine with
AND. Array-valued token claims (groups, token roles…) match when one
element matches.
"identity": {
"claims": {
"email": ["alice@example.com", "a.rolland@example.com"],
"email_verified": "true"
}
}JWT provider
{
"id": "company-api",
"type": "jwt",
"issuer": "https://auth.example.com/t/company",
"jwksUrl": "https://auth.example.com/t/company/jwks.json",
"audience": "https://api.example.com"
}issuer is advertised in metadata; jwksUrl is required; audience
is checked when non-empty. The JWKS is cached (cacheTtlSeconds) and
refreshed on an unknown key.
MCP
An MCP route declares its protected-resource identity:
{
"host": "mcp.example.com",
"path": "/mcp",
"backend": "http://mcp:8080",
"auth": { "provider": "company-api" },
"mcp": {
"enabled": true,
"path": "/mcp",
"name": "things",
"resource": "https://mcp.example.com",
"scope": "mcp-tools",
"operations": [
{ "name": "list_things", "operationId": "listThings" }
]
}
}Gate One then serves:
/.well-known/oauth-protected-resourcemetadata for the endpoint;authorization checks against the bearer token’s
scopeandaud;the declared REST operations as MCP tools;
optional grouping of several services under one endpoint with
mcpSets(the set carries the audience, scope and identity).
An external MCP server can also be proxied with mcp.upstream.url
instead of declared operations.
This article covers the protection. How the tool list is built — deriving
tools from an OpenAPI document, proxying an MCP server, aggregating with
mcpSets — is in MCP tools.
An MCP tool that must call an external API as the user (GitHub,
GitLab, …) uses delegated credentials: upstream.tokenBroker injects the
caller’s per-user token from the token broker into every tool call. See
Delegated credentials.
Complete example
examples/protect-api-mcp/routes.json
shows one browser provider, one JWT provider, an API route with an
identity condition and an MCP service.
Operational notes
A
jwtprovider is stateless: no cookie, no session store, no login host. It is the right choice for machine-to-machine and MCP clients.auth.whenis evaluated before authentication; it cannot inspect the identity. Useauth.identityfor roles/scopes/claims.The MCP audience check fails closed. If the issuer does not put the resource in
aud, configure an audience mapper or usemcpAudience: "auto"with an issuer that supports it.Token issuance, refresh and client registration are the issuer’s job; Gate One only validates.