feat(web): add OAuth 2.1 AS, RS bearer middleware, per-user client #13

Merged
David merged 2 commits from feat/downstream-oauth-as-rs-middleware into main 2026-06-14 22:34:46 +02:00
Owner

Implement the downstream OAuth 2.1 Authorization Server, the Resource-Server bearer gate, and the per-request per-user YouTrack client so Claude can complete OAuth against this binary and call tools as the authenticated user. This is step 4 of the multi-user remote MCP server epic and consumes the step 1-3 scaffolding (transport flag + ClientSource seam, SQLite/crypto layer, Hub OAuth login + allowlist + signed session).

New src/web/oauth_as.rs hand-rolls the AS endpoints: the RFC 9728 protected-resource and RFC 8414 authorization-server discovery documents (S256-only, opaque bearer); RFC 7591 dynamic client registration; GET /authorize, which validates the client and enforces an exact registered + allowlisted redirect-URI match, drives the upstream Hub login when the browser is unauthenticated (resuming the in-flight request on return), and issues a single-use PKCE authorization code; GET /oauth/hub/callback, which completes the allowlist-gated Hub round-trip and resumes /authorize; and POST /token, which implements the authorization_code grant (PKCE verified, code burned atomically against replay) and the refresh_token grant (rotating refresh tokens, old token revoked). Issued access/refresh tokens are opaque, stored only as their SHA-256 hash, and bound to the issuing client and user; the audience is this resource by construction because the AS and RS are the same process and tokens are validated by local lookup, so Hub tokens are never forwarded to Claude.

New src/web/auth_mw.rs is the Resource-Server middleware: it strips and hashes the bearer, looks it up in access_tokens, and on a miss returns 401 with a WWW-Authenticate header carrying resource_metadata and the offline_access scope; on a hit it injects a Principal into the request extensions, which the rmcp Streamable HTTP service copies into each tool call's RequestContext.

src/web/mod.rs adds AppState and build_router, which mounts the rmcp StreamableHttpService at /mcp behind the bearer gate (DNS-rebinding guard anchored on the public host), exposes the OAuth and /healthz routes, and applies CORS, tracing, and a body limit; client_for_user loads the user's Hub token (refreshing if expired) and builds that user's YouTrack client. src/server.rs adds ClientSource::PerUser and an async resolve_client that reads the Principal from the request context, so every tool resolves the per-user client and two users act with their own YouTrack permissions through one server. src/main.rs boots the axum app for the http transport, and src/config.rs adds HttpConfig. Cargo.toml enables rmcp's transport-streamable-http-server feature and adds axum, tower, and tower-http.

#YTMCP-6

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Implement the downstream OAuth 2.1 Authorization Server, the Resource-Server bearer gate, and the per-request per-user YouTrack client so Claude can complete OAuth against this binary and call tools as the authenticated user. This is step 4 of the multi-user remote MCP server epic and consumes the step 1-3 scaffolding (transport flag + ClientSource seam, SQLite/crypto layer, Hub OAuth login + allowlist + signed session). New src/web/oauth_as.rs hand-rolls the AS endpoints: the RFC 9728 protected-resource and RFC 8414 authorization-server discovery documents (S256-only, opaque bearer); RFC 7591 dynamic client registration; GET /authorize, which validates the client and enforces an exact registered + allowlisted redirect-URI match, drives the upstream Hub login when the browser is unauthenticated (resuming the in-flight request on return), and issues a single-use PKCE authorization code; GET /oauth/hub/callback, which completes the allowlist-gated Hub round-trip and resumes /authorize; and POST /token, which implements the authorization_code grant (PKCE verified, code burned atomically against replay) and the refresh_token grant (rotating refresh tokens, old token revoked). Issued access/refresh tokens are opaque, stored only as their SHA-256 hash, and bound to the issuing client and user; the audience is this resource by construction because the AS and RS are the same process and tokens are validated by local lookup, so Hub tokens are never forwarded to Claude. New src/web/auth_mw.rs is the Resource-Server middleware: it strips and hashes the bearer, looks it up in access_tokens, and on a miss returns 401 with a WWW-Authenticate header carrying resource_metadata and the offline_access scope; on a hit it injects a Principal into the request extensions, which the rmcp Streamable HTTP service copies into each tool call's RequestContext. src/web/mod.rs adds AppState and build_router, which mounts the rmcp StreamableHttpService at /mcp behind the bearer gate (DNS-rebinding guard anchored on the public host), exposes the OAuth and /healthz routes, and applies CORS, tracing, and a body limit; client_for_user loads the user's Hub token (refreshing if expired) and builds that user's YouTrack client. src/server.rs adds ClientSource::PerUser and an async resolve_client that reads the Principal from the request context, so every tool resolves the per-user client and two users act with their own YouTrack permissions through one server. src/main.rs boots the axum app for the http transport, and src/config.rs adds HttpConfig. Cargo.toml enables rmcp's transport-streamable-http-server feature and adds axum, tower, and tower-http. #YTMCP-6 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(web): add OAuth 2.1 AS, RS bearer middleware, per-user client
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 26s
6631052ffd
Implement the downstream OAuth 2.1 Authorization Server, the Resource-Server bearer gate, and the per-request per-user YouTrack client so Claude can complete OAuth against this binary and call tools as the authenticated user. This is step 4 of the multi-user remote MCP server epic and consumes the step 1-3 scaffolding (transport flag + ClientSource seam, SQLite/crypto layer, Hub OAuth login + allowlist + signed session).

New src/web/oauth_as.rs hand-rolls the AS endpoints: the RFC 9728 protected-resource and RFC 8414 authorization-server discovery documents (S256-only, opaque bearer); RFC 7591 dynamic client registration; GET /authorize, which validates the client and enforces an exact registered + allowlisted redirect-URI match, drives the upstream Hub login when the browser is unauthenticated (resuming the in-flight request on return), and issues a single-use PKCE authorization code; GET /oauth/hub/callback, which completes the allowlist-gated Hub round-trip and resumes /authorize; and POST /token, which implements the authorization_code grant (PKCE verified, code burned atomically against replay) and the refresh_token grant (rotating refresh tokens, old token revoked). Issued access/refresh tokens are opaque, stored only as their SHA-256 hash, and bound to the issuing client and user; the audience is this resource by construction because the AS and RS are the same process and tokens are validated by local lookup, so Hub tokens are never forwarded to Claude.

New src/web/auth_mw.rs is the Resource-Server middleware: it strips and hashes the bearer, looks it up in access_tokens, and on a miss returns 401 with a WWW-Authenticate header carrying resource_metadata and the offline_access scope; on a hit it injects a Principal into the request extensions, which the rmcp Streamable HTTP service copies into each tool call's RequestContext.

src/web/mod.rs adds AppState and build_router, which mounts the rmcp StreamableHttpService at /mcp behind the bearer gate (DNS-rebinding guard anchored on the public host), exposes the OAuth and /healthz routes, and applies CORS, tracing, and a body limit; client_for_user loads the user's Hub token (refreshing if expired) and builds that user's YouTrack client. src/server.rs adds ClientSource::PerUser and an async resolve_client that reads the Principal from the request context, so every tool resolves the per-user client and two users act with their own YouTrack permissions through one server. src/main.rs boots the axum app for the http transport, and src/config.rs adds HttpConfig. Cargo.toml enables rmcp's transport-streamable-http-server feature and adds axum, tower, and tower-http.

#YTMCP-6

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge branch 'main' into feat/downstream-oauth-as-rs-middleware
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
Check / fmt + clippy + build + tests (pull_request) Successful in 52s
9cfadedb47
David merged commit 953bf82b70 into main 2026-06-14 22:34:46 +02:00
David deleted branch feat/downstream-oauth-as-rs-middleware 2026-06-14 22:34:46 +02:00
Commenting is not possible because the repository is archived.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
pandoras-box/youtrack-mcp!13
No description provided.