Files
Gondulf/docs/designs/token-verification-endpoint.md
Phil Skentelbery 6bb2a4033f feat(token): implement GET /token for token verification
Implements W3C IndieAuth Section 6.3 token verification endpoint.
The token endpoint now supports both:
- POST: Issue new tokens (authorization code exchange)
- GET: Verify existing tokens (resource server validation)

Changes:
- Added GET handler to /token endpoint
- Extracts Bearer token from Authorization header (RFC 6750)
- Returns JSON with me, client_id, scope
- Returns 401 with WWW-Authenticate for invalid tokens
- 11 new tests covering all verification scenarios

All 533 tests passing. Resolves critical P0 blocker for v1.0.0.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 08:10:47 -07:00

346 lines
8.9 KiB
Markdown

# Design: Token Verification Endpoint (Critical Compliance Fix)
**Date**: 2025-11-25
**Architect**: Claude (Architect Agent)
**Status**: Ready for Immediate Implementation
**Priority**: P0 - CRITICAL BLOCKER
**Design Version**: 1.0
## Executive Summary
**CRITICAL COMPLIANCE BUG**: Gondulf's token endpoint does not support GET requests for token verification, violating the W3C IndieAuth specification. This prevents resource servers (like Micropub endpoints) from verifying tokens, making our access tokens useless.
**Fix Required**: Add GET handler to `/token` endpoint that verifies Bearer tokens per specification.
## Problem Statement
### What's Broken
1. **Current State**:
- POST `/token` works (issues tokens)
- GET `/token` returns 405 Method Not Allowed
- Resource servers cannot verify our tokens
- Micropub/Microsub integration fails
2. **Specification Requirement** (W3C IndieAuth Section 6.3):
> "If an external endpoint needs to verify that an access token is valid, it MUST make a GET request to the token endpoint containing an HTTP Authorization header with the Bearer Token"
3. **Impact**:
- Gondulf is NOT IndieAuth-compliant
- Access tokens are effectively useless
- Integration with any resource server fails
## Solution Design
### API Endpoint
**GET /token**
**Purpose**: Verify access token validity for resource servers
**Headers Required**:
```
Authorization: Bearer {access_token}
```
**Success Response (200 OK)**:
```json
{
"me": "https://example.com",
"client_id": "https://client.example.com",
"scope": ""
}
```
**Error Response (401 Unauthorized)**:
```json
{
"error": "invalid_token"
}
```
### Implementation
**File**: `/src/gondulf/routers/token.py` (UPDATE EXISTING)
**Add this handler**:
```python
from fastapi import Header
@router.get("/token")
async def verify_token(
authorization: Optional[str] = Header(None),
token_service: TokenService = Depends(get_token_service)
) -> dict:
"""
Verify access token per W3C IndieAuth specification.
Per https://www.w3.org/TR/indieauth/#token-verification:
"If an external endpoint needs to verify that an access token is valid,
it MUST make a GET request to the token endpoint containing an HTTP
Authorization header with the Bearer Token"
Request:
GET /token
Authorization: Bearer {access_token}
Response (200 OK):
{
"me": "https://example.com",
"client_id": "https://client.example.com",
"scope": ""
}
Error Response (401 Unauthorized):
{
"error": "invalid_token"
}
Args:
authorization: Authorization header with Bearer token
token_service: Token validation service
Returns:
Token metadata if valid
Raises:
HTTPException: 401 for invalid/missing token
"""
# Log verification attempt
logger.debug("Token verification request received")
# STEP 1: Extract Bearer token from Authorization header
if not authorization:
logger.warning("Token verification failed: Missing Authorization header")
raise HTTPException(
status_code=401,
detail={"error": "invalid_token"},
headers={"WWW-Authenticate": "Bearer"}
)
# Check for Bearer prefix (case-insensitive per RFC 6750)
if not authorization.lower().startswith("bearer "):
logger.warning(f"Token verification failed: Invalid auth scheme (expected Bearer)")
raise HTTPException(
status_code=401,
detail={"error": "invalid_token"},
headers={"WWW-Authenticate": "Bearer"}
)
# Extract token (everything after "Bearer ")
# Handle both "Bearer " and "bearer " per RFC 6750
token = authorization[7:].strip()
if not token:
logger.warning("Token verification failed: Empty token")
raise HTTPException(
status_code=401,
detail={"error": "invalid_token"},
headers={"WWW-Authenticate": "Bearer"}
)
# STEP 2: Validate token using existing service
try:
metadata = token_service.validate_token(token)
except Exception as e:
logger.error(f"Token verification error: {e}")
raise HTTPException(
status_code=401,
detail={"error": "invalid_token"},
headers={"WWW-Authenticate": "Bearer"}
)
# STEP 3: Check if token is valid
if not metadata:
logger.info(f"Token verification failed: Invalid or expired token (prefix: {token[:8]}...)")
raise HTTPException(
status_code=401,
detail={"error": "invalid_token"},
headers={"WWW-Authenticate": "Bearer"}
)
# STEP 4: Return token metadata per specification
logger.info(f"Token verified successfully for {metadata['me']}")
return {
"me": metadata["me"],
"client_id": metadata["client_id"],
"scope": metadata.get("scope", "")
}
```
### No Other Changes Required
The existing `TokenService.validate_token()` method already:
- Hashes the token
- Looks it up in the database
- Checks expiration
- Checks revocation status
- Returns metadata or None
No changes needed to the service layer.
## Data Flow
```
Resource Server (e.g., Micropub)
│ GET /token
│ Authorization: Bearer abc123...
Token Endpoint (GET)
│ Extract token from header
Token Service
│ Hash token
│ Query database
│ Check expiration
Return Metadata
│ 200 OK
│ {
│ "me": "https://example.com",
│ "client_id": "https://client.com",
│ "scope": ""
│ }
Resource Server
(Allows/denies access)
```
## Testing Requirements
### Unit Tests (5 tests)
1. **Valid Token**:
- Input: Valid Bearer token
- Expected: 200 OK with metadata
2. **Invalid Token**:
- Input: Non-existent token
- Expected: 401 Unauthorized
3. **Expired Token**:
- Input: Expired token
- Expected: 401 Unauthorized
4. **Missing Header**:
- Input: No Authorization header
- Expected: 401 Unauthorized
5. **Invalid Header Format**:
- Input: "Basic xyz" or malformed
- Expected: 401 Unauthorized
### Integration Tests (3 tests)
1. **Full Flow**:
- POST /token to get token
- GET /token to verify it
- Verify metadata matches
2. **Revoked Token**:
- Create token, revoke it
- GET /token should fail
3. **Cross-Client Verification**:
- Token from client A
- Verify returns client_id A
### Manual Testing
Test with real Micropub client:
1. Authenticate with Gondulf
2. Get access token
3. Configure Micropub client
4. Verify it can post successfully
## Security Considerations
### RFC 6750 Compliance
- Accept both "Bearer" and "bearer" (case-insensitive)
- Return WWW-Authenticate header on 401
- Don't leak token details in errors
- Log only token prefix (8 chars)
### Error Handling
All errors return 401 with `{"error": "invalid_token"}`:
- Missing header
- Wrong auth scheme
- Invalid token
- Expired token
- Revoked token
This prevents token enumeration attacks.
### Rate Limiting
Consider adding rate limiting in future:
- Per IP: 100 requests/minute
- Per token: 10 requests/minute
Not critical for v1.0.0 but recommended for v1.1.0.
## Implementation Checklist
- [ ] Add GET handler to `/src/gondulf/routers/token.py`
- [ ] Import Header from fastapi
- [ ] Implement Bearer token extraction
- [ ] Call existing validate_token() method
- [ ] Return required JSON format
- [ ] Add unit tests (5)
- [ ] Add integration tests (3)
- [ ] Test with real Micropub client
- [ ] Update API documentation
## Effort Estimate
**Total**: 1-2 hours
- Implementation: 30 minutes
- Testing: 45 minutes
- Documentation: 15 minutes
- Manual verification: 30 minutes
## Acceptance Criteria
### Mandatory for v1.0.0
- [ ] GET /token accepts Bearer token
- [ ] Returns correct JSON format
- [ ] Returns 401 for invalid tokens
- [ ] All tests passing
- [ ] Micropub client can verify tokens
### Success Metrics
- StarPunk's Micropub works with Gondulf
- Any IndieAuth resource server accepts our tokens
- Full W3C specification compliance
## Why This is Critical
Without token verification:
1. **Access tokens are useless** - No way to verify them
2. **Not IndieAuth-compliant** - Violates core specification
3. **No Micropub/Microsub** - Integration impossible
4. **Defeats the purpose** - Why issue tokens that can't be verified?
## Related Documents
- ADR-013: Token Verification Endpoint Missing
- W3C IndieAuth: https://www.w3.org/TR/indieauth/#token-verification
- RFC 6750: https://datatracker.ietf.org/doc/html/rfc6750
- Existing Token Service: `/src/gondulf/services/token_service.py`
---
**DESIGN READY: Token Verification Endpoint - CRITICAL FIX REQUIRED**
This must be implemented immediately to achieve IndieAuth compliance.