Authorization Server - ChristianPresley/zig-sdk GitHub Wiki
mcp.auth.AuthorizationServer is an OAuth 2.11 authorization server for MCP deployments. It issues JWT access tokens for the MCP servers of the deployment. The resource server helpers of the SDK verify these tokens with its JWK set. See Authorization for the client side.
Applies to: MCP 2026-07-28, zig-sdk 0.2.0.
- It serves the metadata2 at
/.well-known/oauth-authorization-serverand at the OpenID Connect paths. - The authorization endpoint runs the code flow with PKCE S2563 and the
issparameter4. - The token endpoint offers
authorization_code,refresh_token,client_credentialsand the JWT bearer grant5 for ID-JAGs and workload JWTs. - Each grant can give a DPoP-bound token6.
-
/jwkspublishes the public signing keys.
The tokens have the typ header at+jwt10. The aud claim is the MCP server7.
- Load a signing key, or make one with
generateSigningKey(io). - Write the options: the issuer, the keys, the MCP servers, the scopes and the authorizer.
- Call
AuthorizationServer.init. - Call
listenandserve. Or callhandlefrom the HTTP server of your application. - Give the public keys of the server to the
JwtVerifierof each MCP server. Set itstoken_typetoat+jwt.
In the same process, verificationKeys(arena) gives the keys for the JwtVerifier. Another process reads the JWK set at /jwks with mcp.auth.jwt.parseJwks.
const keys = [_]as.SigningKey{.{ .key = &key, .kid = "2026-10" }};
const resources = [_]as.Resource{.{ .uri = "https://mcp.example.com/mcp" }};
var server = try mcp.auth.AuthorizationServer.init(io, gpa, .{
.issuer = "https://auth.example.com",
.signing_keys = &keys,
.resources = &resources,
.scopes_supported = &.{ "mcp:read", "mcp:write" },
.authorizer = .{ .userdata = &app, .decide = App.decide },
});
defer server.deinit();
try server.listen(.{ .port = 8443, .tls = &tls_server });
try server.serve();Here as is mcp.auth.authorization_server. The example examples/authorization_server.zig runs the server next to an MCP server.
| Option | What it does |
|---|---|
issuer |
An https URL without a query, a fragment or a slash at the end. |
signing_keys |
The first key signs. The JWK set has all keys, for a key rotation. |
resources |
The MCP servers. A request without resource gets the first one. |
scopes_supported, default_scopes
|
The scopes that clients can get, and the scopes of a request without scope. |
authorizer |
User authentication and consent. Null disables the authorization endpoint. |
clients |
The clients to register at the start. |
grants |
The grants, and the validators of the JWT bearer grant. |
dpop |
DPoP at the token endpoint, with an optional nonce. |
dynamic_registration |
Null by default. |
client_metadata |
Client ID metadata documents8. Enabled by default. |
store |
The store. Null uses a MemoryStore. |
allow_http |
Accept a loopback http issuer. Use it for tests only. |
The lifetimes are 900 seconds for access tokens, 60 seconds for codes and 30 days for a grant family.
The server checks the client, the redirect URI, PKCE, the resource and the scopes. Then it calls the decide function of the authorizer. The function returns approve with a subject, deny, or respond with a page of the application. A consent form sends its answer to the authorization endpoint with POST. hiddenFields gives the OAuth parameters for the form. consentToken and checkConsentToken bind the form to a secret value of the browser.
AutoApprove approves each request for one subject. Use it for tests only.
The client authentication methods are none, client_secret_basic, client_secret_post and private_key_jwt. The jti of a client assertion is single use. A client_id that is an https URL names a client ID metadata document. MCP deprecates dynamic client registration9, so the server disables it by default.
authorization_store.Store has the functions get_client, put_client, put_code, take_code, put_refresh_token, take_refresh_token, revoke_family and record_jti. A store keeps SHA-256 hashes of codes, refresh tokens and client secrets. MemoryStore has a limit for each map and removes expired records.
- The server compares redirect URIs exactly. A loopback
httpURI can have any port11. - The server refuses PKCE
plainand a request without a challenge. - A code that comes a second time revokes the refresh tokens of its grant.
- The server rotates refresh tokens. A used token that comes again revokes its grant family.
- The fetch of a client document accepts only https and public addresses. It does not follow redirects, and it has a size limit and a time limit.
- Each token response has
Cache-Control: no-store. The log has no tokens or secrets.
The conformance suite has two authorization server scenarios: authorization-server-metadata-endpoint and authorization-code-grant. The CI job conformance runs both against the fixture mcp-conformance-authorization-server, and both pass. See Conformance.
- A revocation stops refresh tokens. The access tokens stay valid until they expire.
- The server has no introspection and no revocation endpoint.
- A request names one resource only.
-
MemoryStoreloses all records at a restart.
- OAUTH21-DRAFT: The OAuth 2.1 Authorization Framework.
- RFC8414: OAuth 2.0 Authorization Server Metadata.
- RFC7636: Proof Key for Code Exchange.
- RFC9207: OAuth 2.0 Authorization Server Issuer Identification.
- RFC7523: JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants.
- RFC9449: OAuth 2.0 DPoP.
- RFC8707: Resource Indicators for OAuth 2.0.
- OAUTH-CIMD-DRAFT: Client ID Metadata Documents.
- RFC7591: OAuth 2.0 Dynamic Client Registration Protocol.
- RFC9068: JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens.
- RFC8252: OAuth 2.0 for Native Apps.