-
Notifications
You must be signed in to change notification settings - Fork 26
feat: authorization via oauth server-side #201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
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 introduces a comprehensive OAuth 2.1 authorization implementation for the Hermes MCP server, enabling secure authentication and authorization for HTTP transports using bearer tokens, JWT validation, and introspection methods.
- Implements OAuth 2.1 authorization with pluggable validators (JWT and introspection)
- Adds comprehensive authentication/authorization utilities and frame helpers
- Includes a complete example application demonstrating OAuth integration
Reviewed Changes
Copilot reviewed 32 out of 34 changed files in this pull request and generated 8 comments.
Show a summary per file
File | Description |
---|---|
lib/hermes/server/authorization.ex | Core authorization module with config parsing and validation logic |
lib/hermes/server/authorization/plug.ex | Plug implementation for HTTP transport authorization |
lib/hermes/server/authorization/jwt_validator.ex | JWT token validator with JWKS support |
lib/hermes/server/authorization/introspection_validator.ex | OAuth introspection validator |
lib/hermes/server/authorization/validator.ex | Validator behavior definition |
lib/hermes/server/frame.ex | Adds auth helper functions to Frame module |
lib/hermes/server/transport/streamable_http/plug.ex | Integrates authorization into HTTP transport |
lib/hermes/server/component/resource.ex | Updates JSON encoding for consistency |
priv/dev/oauth_example/* | Complete OAuth example application |
test/* | Test updates for JSON encoding consistency |
case Process.get(cache_key) do | ||
{jwks, expires_at} when expires_at > now -> | ||
{:ok, jwks} | ||
|
||
_ -> | ||
with {:ok, jwks} <- fetch_jwks_from_uri(jwks_uri) do | ||
expires_at = System.monotonic_time(:millisecond) + @jwks_cache_ttl | ||
Process.put(cache_key, {jwks, expires_at}) |
Copilot
AI
Jul 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using Process.put
for caching creates process-local storage that won't be shared across different processes. For a production application, consider using ETS or a proper cache like ConCache to enable shared caching.
case Process.get(cache_key) do | |
{jwks, expires_at} when expires_at > now -> | |
{:ok, jwks} | |
_ -> | |
with {:ok, jwks} <- fetch_jwks_from_uri(jwks_uri) do | |
expires_at = System.monotonic_time(:millisecond) + @jwks_cache_ttl | |
Process.put(cache_key, {jwks, expires_at}) | |
case :ets.lookup(:jwks_cache, cache_key) do | |
[{^cache_key, {jwks, expires_at}}] when expires_at > now -> | |
{:ok, jwks} | |
_ -> | |
with {:ok, jwks} <- fetch_jwks_from_uri(jwks_uri) do | |
expires_at = System.monotonic_time(:millisecond) + @jwks_cache_ttl | |
:ets.insert(:jwks_cache, {cache_key, {jwks, expires_at}}) |
Copilot uses AI. Check for mistakes.
{_, port} -> ":#{port}" | ||
end | ||
|
||
"#{scheme}://#{conn.host}#{port_part}#{conn.request_path}" |
Copilot
AI
Jul 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using conn.request_path
in the server URI for audience validation could be manipulated by clients. Consider using a configured base URI or only the scheme/host/port portions.
"#{scheme}://#{conn.host}#{port_part}#{conn.request_path}" | |
base_uri = config[:base_uri] || "#{scheme}://#{conn.host}#{port_part}" | |
base_uri |
Copilot uses AI. Check for mistakes.
String.replace(string, ~s("), ~s(\\")) | ||
end | ||
|
||
defp get_canonical_server_uri do |
Copilot
AI
Jul 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function returns a placeholder URI that should be configurable. Consider making this part of the authorization configuration rather than relying on an environment variable.
Copilot uses AI. Check for mistakes.
{:ok, | ||
%{ | ||
sub: "user_read_only", | ||
aud: "https://localhost:4001", |
Copilot
AI
Jul 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hardcoded audience URL should be configurable or extracted to a module attribute to avoid duplication and make it easier to change.
aud: "https://localhost:4001", | |
aud: @audience_url, |
Copilot uses AI. Check for mistakes.
|
||
children = [ | ||
Hermes.Server.Registry, | ||
{OauthExample.Server, transport: {:streamable_http, authorization: auth_config}}, |
Copilot
AI
Jul 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The authorization configuration could be extracted to application config (config/config.exs) to make it easier to manage different environments.
Copilot uses AI. Check for mistakes.
This pull request introduces a comprehensive implementation of OAuth 2.1 authorization for the Hermes server, including token validation, audience checks, expiry handling, and support for multiple validation methods (JWT, introspection, etc.). It also adds utility modules for authorization configuration and metadata handling, and updates existing code for consistency with the new functionality.
OAuth 2.1 Authorization Implementation
Core Authorization Features:
lib/hermes/server/authorization.ex
: Implements token validation logic, including audience checks, expiry validation, and scope enforcement. Provides utility functions to parse authorization configurations and build WWW-Authenticate headers for 401 responses.Validation Methods:
lib/hermes/server/authorization/introspection_validator.ex
: Adds support for validating opaque tokens using the OAuth 2.0 Token Introspection endpoint (RFC 7662). Includes real-time revocation detection and client authentication.lib/hermes/server/authorization/jwt_validator.ex
: Implements JWT validation using public keys fetched from JWKS endpoints. Supports RSA and ECDSA algorithms, caching, and standard claims validation (e.g.,exp
,iss
,aud
).Plug Integration:
lib/hermes/server/authorization/plug.ex
: Provides aPlug
module for integrating authorization into the request pipeline. Handles token extraction, validation, and metadata responses for well-known paths.Supporting Changes
Custom Validators:
lib/hermes/server/authorization/validator.ex
: Defines a behavior for creating custom token validators, enabling extensibility for different validation mechanisms.Code Consistency:
lib/hermes/server/component/resource.ex
: ReplacesJason.encode!
withJSON.encode!
for consistency across the codebase.Documentation Update:
README.md
: Adds an example of aplug
-based MCP server implementation with OAuth authorization.