feat(oauth): Phase B — claude.ai protocol compatibility
Add 3 features for claude.ai Connectors compatibility: - FEATURE-3: client_secret_basic auth on token endpoint (RFC 6749 §2.3.1) - FEATURE-2: Token revocation endpoint /oauth/revoke (RFC 7009) - FEATURE-4: resource parameter support with JWT aud claim (RFC 8707) 23 new tests (481 total), all passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -63,6 +63,7 @@ class AuthorizationCode(BaseModel):
|
||||
api_key_id: str | None = None # API Key ID for scope/project inheritance
|
||||
api_key_project_id: str | None = None # Project ID from API Key
|
||||
api_key_scope: str | None = None # Scope from API Key
|
||||
resource: str | None = None # RFC 8707: Resource indicator
|
||||
|
||||
def is_expired(self) -> bool:
|
||||
"""Check if code is expired"""
|
||||
|
||||
@@ -143,6 +143,7 @@ class OAuthServer:
|
||||
api_key_id: str | None = None,
|
||||
api_key_project_id: str | None = None,
|
||||
api_key_scope: str | None = None,
|
||||
resource: str | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
Create authorization code (Step 2 of Authorization Code flow)
|
||||
@@ -157,6 +158,7 @@ class OAuthServer:
|
||||
api_key_id: Optional API Key ID for scope/project inheritance
|
||||
api_key_project_id: Optional project ID from API Key
|
||||
api_key_scope: Optional scope from API Key
|
||||
resource: Optional resource indicator (RFC 8707)
|
||||
|
||||
Returns:
|
||||
Authorization code (valid for 5 minutes)
|
||||
@@ -178,6 +180,7 @@ class OAuthServer:
|
||||
api_key_id=api_key_id,
|
||||
api_key_project_id=api_key_project_id,
|
||||
api_key_scope=api_key_scope,
|
||||
resource=resource,
|
||||
)
|
||||
|
||||
# Save to storage
|
||||
@@ -257,12 +260,14 @@ class OAuthServer:
|
||||
# If authorization code has API Key metadata, use it for scoping
|
||||
project_id = auth_code.api_key_project_id or "*"
|
||||
token_scope = auth_code.api_key_scope or auth_code.scope
|
||||
resource = auth_code.resource
|
||||
|
||||
access_token = self.token_manager.generate_access_token(
|
||||
client_id=client_id,
|
||||
scope=token_scope,
|
||||
user_id=auth_code.user_id or auth_code.api_key_id,
|
||||
project_id=project_id,
|
||||
resource=resource,
|
||||
)
|
||||
|
||||
refresh_token = self.token_manager.generate_refresh_token(
|
||||
|
||||
@@ -54,7 +54,12 @@ class TokenManager:
|
||||
self.refresh_token_ttl = int(os.getenv("OAUTH_REFRESH_TOKEN_TTL", "604800")) # 7 days
|
||||
|
||||
def generate_access_token(
|
||||
self, client_id: str, scope: str, user_id: str | None = None, project_id: str = "*"
|
||||
self,
|
||||
client_id: str,
|
||||
scope: str,
|
||||
user_id: str | None = None,
|
||||
project_id: str = "*",
|
||||
resource: str | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
Generate JWT access token.
|
||||
@@ -64,6 +69,7 @@ class TokenManager:
|
||||
scope: Granted scopes (space-separated)
|
||||
user_id: User ID (optional, for user-based auth)
|
||||
project_id: Project ID for scoping (default: "*" for global)
|
||||
resource: Resource indicator for aud claim (RFC 8707)
|
||||
|
||||
Returns:
|
||||
JWT access token
|
||||
@@ -87,6 +93,9 @@ class TokenManager:
|
||||
if user_id:
|
||||
payload["sub"] = user_id # Subject (user ID)
|
||||
|
||||
if resource:
|
||||
payload["aud"] = resource
|
||||
|
||||
# Encode JWT
|
||||
token = jwt.encode(payload, self.jwt_secret, algorithm=self.jwt_algorithm)
|
||||
|
||||
|
||||
@@ -68,6 +68,9 @@
|
||||
{% if state %}
|
||||
<input type="hidden" name="state" value="{{ state }}">
|
||||
{% endif %}
|
||||
{% if resource %}
|
||||
<input type="hidden" name="resource" value="{{ resource }}">
|
||||
{% endif %}
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
|
||||
<!-- API Key Input -->
|
||||
|
||||
Reference in New Issue
Block a user