Skip to content

Front-Door Auth#696

Merged
EricGustin merged 30 commits into
mainfrom
ericgustin/front-door-auth
Dec 11, 2025
Merged

Front-Door Auth#696
EricGustin merged 30 commits into
mainfrom
ericgustin/front-door-auth

Conversation

@EricGustin

@EricGustin EricGustin commented Nov 25, 2025

Copy link
Copy Markdown
Member

Valuable references for the reviewer:

demovideo.mp4

PR Description

Adds OAuth 2.1 Resource Server authentication to arcade-mcp-server, enabling HTTP MCP servers to validate Bearer tokens on every request. This unlocks tool-level authorization and secrets support for HTTP servers.

  • Multiple authorization server support
  • Granular token validation options (verify_exp, verify_iat, verify_iss)
  • Environment variable configuration
  • OAuth discovery metadata endpoint (/.well-known/oauth-protected-resource)
  • Extracts sub claim from token as context.user_id
  • Lifts transport restrictions for tools requiring auth/secrets on HTTP when protected
from arcade_mcp_server import MCPApp
from arcade_mcp_server.resource_server import ResourceServerAuth, AuthorizationServerEntry

resource_server_auth = ResourceServerAuth(
    canonical_url="http://127.0.0.1:8000/mcp",
    authorization_servers=[
        AuthorizationServerEntry(
            authorization_server_url="https://auth.example.com",
            issuer="https://auth.example.com",
            jwks_uri="https://auth.example.com/jwks",
        )
    ],
)

app = MCPApp(name="my_server", version="1.0.0", auth=resource_server_auth)

Testing

Beyond the comprehensive unit tests, I also manually tested end-to-end with WorkOS Authkit (DCR) and KeyCloak (non-DCR).

Future Work

  • CIMD support
  • An ArcadeResourceServer to make adding front-door auth super easy when using Arcade's Auth Server

Note

Adds OAuth 2.1 front-door auth (JWKS validation + OAuth discovery) and propagates user identity to tools, enabling auth/secret-requiring tools over HTTP.

  • Authentication (Front-Door OAuth 2.1)
    • New resource_server module with ResourceServerAuth (multi-authorization-server, metadata) and JWKSTokenValidator (JWKS-based JWT validation) plus granular validation options.
    • ASGI ResourceServerMiddleware validates Bearer tokens on every HTTP request and injects resource_owner.
    • OAuth discovery endpoint via FastAPI router at /.well-known/oauth-protected-resource[/<path>].
  • Integration
    • MCPApp/worker accept auth/resource_server_validator, mount middleware, expose discovery; logs accepted auth servers.
    • HTTP transport (http_streamable) carries SessionMessage with resource_owner from request → session.
    • Context/Session/Server plumb resource_owner; Server selects user_id preferring token sub.
  • Behavior Changes
    • HTTP transport restriction lifted for tools requiring authorization/secrets when request is authenticated; otherwise blocked with actionable error.
  • Configuration
    • Env-var based auth config via MCP_RESOURCE_SERVER_* in MCPSettings.ResourceServerSettings; .env auto-load.
  • Telemetry
    • Usage tracking records resource_server_type on server start.
  • Examples
    • New examples/mcp_servers/authorization sample server (HTTP auth, secrets, Reddit tool) with Docker setup.
  • Tests
    • Extensive unit tests for validators, middleware, env config, multi-AS, transport rules, and app integration.
  • Version
    • Bump arcade-mcp-server to 1.12.0; minor docstring tweak in __init__.py.

Written by Cursor Bugbot for commit d1116cd. This will update automatically on new commits. Configure here.

Resolves TOO-152

@EricGustin EricGustin requested a review from Copilot November 25, 2025 03:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements "Front-Door Authentication" for the Arcade MCP server, adding OAuth 2.1-compliant server-level authentication for HTTP transport. The implementation validates Bearer tokens on every HTTP request before processing MCP protocol messages, enabling secure HTTP transport with tool-level authorization and OAuth discovery support.

Key Changes:

  • Added JWT-based token verification with JWKS support for validating Bearer tokens
  • Implemented OAuth 2.0 Protected Resource Metadata (RFC 9728) for client discovery
  • Added support for multiple authorization servers with flexible configuration via environment variables or code

Reviewed changes

Copilot reviewed 28 out of 29 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
libs/tests/arcade_mcp_server/test_server_auth.py Comprehensive test suite for JWT verification, RemoteOAuthProvider, middleware, and multi-authorization server scenarios
libs/tests/arcade_mcp_server/test_server.py Added authenticated user context to HTTP transport tests
libs/tests/arcade_mcp_server/test_mcp_app.py Updated tests to include server_auth_provider parameter
libs/arcade-mcp-server/pyproject.toml Added python-jose and httpx dependencies
libs/arcade-mcp-server/arcade_mcp_server/worker.py Integrated auth middleware and OAuth discovery routes
libs/arcade-mcp-server/arcade_mcp_server/types.py Extended SessionMessage to carry authenticated user info
libs/arcade-mcp-server/arcade_mcp_server/transports/http_streamable.py Modified to extract and pass authenticated user from scope
libs/arcade-mcp-server/arcade_mcp_server/settings.py Added ServerAuthSettings for environment-based configuration
libs/arcade-mcp-server/arcade_mcp_server/session.py Updated to handle authenticated user context per request
libs/arcade-mcp-server/arcade_mcp_server/server_auth/ New module with base classes, JWT verifier, RemoteOAuthProvider, and ASGI middleware
libs/arcade-mcp-server/arcade_mcp_server/server.py Enhanced to use authenticated user from front-door auth for tool context
libs/arcade-mcp-server/arcade_mcp_server/mcp_app.py Added auth parameter and removed duplicate .env loading
libs/arcade-mcp-server/arcade_mcp_server/fastapi/auth_routes.py OAuth discovery endpoint implementation
libs/arcade-mcp-server/arcade_mcp_server/context.py Added authenticated_user to Context initialization
examples/mcp_servers/authorization/ Complete example server demonstrating front-door auth with Docker setup

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread libs/arcade-mcp-server/arcade_mcp_server/settings.py
@EricGustin EricGustin marked this pull request as ready for review December 3, 2025 00:08

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is being reviewed by Cursor Bugbot

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Comment thread libs/arcade-mcp-server/arcade_mcp_server/server.py
Comment thread libs/arcade-mcp-server/arcade_mcp_server/session.py Outdated
Comment thread libs/arcade-mcp-server/arcade_mcp_server/resource_server/middleware.py Outdated
Comment thread libs/arcade-mcp-server/arcade_mcp_server/resource_server/middleware.py Outdated
cursor[bot]

This comment was marked as off-topic.

- ARCADE_SERVER_TRANSPORT=http
- ARCADE_SERVER_HOST=0.0.0.0
- ARCADE_SERVER_PORT=8001
- MCP_RESOURCE_SERVER_CANONICAL_URL=http://127.0.0.1:8080/mcp

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably out of scope of this PR, but I've generally seen this as PROXY_URL or something for when the server is behind a proxy of any kind.

@wdawson wdawson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auth stuff looks good to me! Some optimization potential in a comment!

I did not really review the devex side. I figure you want a non-auth person to do that

Returns:
Dictionary containing resource metadata per RFC 9728
"""
return {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming this doesn't change, I would construct this at init time so that it can be returned very efficiently.

Then, some other values to consider adding to the document now:

  • scopes_supported: Should contain the scopes required to access the MCP server itself (maybe just mcp?; not the tool scopes! — seems like we aren't validating scopes at all right now though, so maybe not worth it!
  • bearer_methods_supported: Probably just header?

And some more to consider in the future:

  • resource_name: A human readable name to display to the end user; could be settable by some config somewhere?
  • resource_documentation: A URL for docs about this MCP server; config?
  • resource_policy_uri: A URL for a human readable policy document; config?
  • resource_tos_uri: A URL for terms of service; config?

@EricGustin EricGustin Dec 5, 2025

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I implemented the following:

  1. construct resource metadata at init time
  2. added bearer_methods_supported

For the others:

  1. Holding off on scopes entirely for now
  2. Will add a mention of resource_* to planning document for phase 2

Comment thread libs/arcade-mcp-server/arcade_mcp_server/fastapi/auth_routes.py
Comment thread libs/arcade-mcp-server/arcade_mcp_server/settings.py
Comment thread examples/mcp_servers/authorization/src/authorization/.env.example
Comment thread examples/mcp_servers/authorization/src/authorization/server.py Outdated
Comment thread examples/mcp_servers/authorization/src/authorization/server.py Outdated
# ```
# resource_server = ResourceServer()

app = MCPApp(name="authorization", version="1.0.0", log_level="DEBUG", auth=resource_server)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nitty nit: It feels weird to pass a ResourceServer to auth, because what I'm building here (the MCP server) is a resource server. Maybe ResourceServerAuthorization as a class name instead?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not nitty at all! I'm going to go with ResourceServerAuth

Comment thread libs/arcade-mcp-server/arcade_mcp_server/fastapi/auth_routes.py
Comment thread libs/arcade-mcp-server/arcade_mcp_server/resource_server/README.md Outdated
that are not compliant with MCP.

Note: Token signature verification is always enabled and cannot be disabled.
Additionally, the subject (sub claim) must always be present in the token.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may have to lift the sub claim requirement - we'll see. Some AS don't return it, or return some non-standard claim name. I think it's OK to make this required for now.

Comment thread libs/arcade-mcp-server/arcade_mcp_server/resource_server/middleware.py Outdated
Comment thread libs/arcade-mcp-server/arcade_mcp_server/resource_server/validators/jwks.py Outdated
Comment thread examples/mcp_servers/simple/pyproject.toml Outdated
Comment thread examples/mcp_servers/authorization/src/authorization/server.py Outdated
issuer="https://your-workos.authkit.app",
jwks_uri="https://your-workos.authkit.app/oauth2/jwks",
validation_options=AccessTokenValidationOptions(verify_aud=False),
expected_audiences=["your-authkit-client-id"], # Override expected aud claim

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: from what we were discussing yesterday it seems like in the WorkOS case it's an app or workspace ID, not a client ID right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So their naming of things is very bad, but it is the AuthKit's Client ID (which is closer to a workspace ID), not the registered client's ID. Both IDs are unfortunately prefixed with client_ lol

@EricGustin EricGustin merged commit 98fd13c into main Dec 11, 2025
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants