Completed all remaining phases of ADR-030 IndieAuth provider removal. StarPunk no longer acts as an authorization server - all IndieAuth operations delegated to external providers. Phase 2 - Remove Token Issuance: - Deleted /auth/token endpoint - Removed token_endpoint() function from routes/auth.py - Deleted tests/test_routes_token.py Phase 3 - Remove Token Storage: - Deleted starpunk/tokens.py module entirely - Created migration 004 to drop tokens and authorization_codes tables - Deleted tests/test_tokens.py - Removed all internal token CRUD operations Phase 4 - External Token Verification: - Created starpunk/auth_external.py module - Implemented verify_external_token() for external IndieAuth providers - Updated Micropub endpoint to use external verification - Added TOKEN_ENDPOINT configuration - Updated all Micropub tests to mock external verification - HTTP timeout protection (5s) for external requests Additional Changes: - Created migration 003 to remove code_verifier from auth_state - Fixed 5 migration tests that referenced obsolete code_verifier column - Updated 11 Micropub tests for external verification - Fixed test fixture and app context issues - All 501 tests passing Breaking Changes: - Micropub clients must use external IndieAuth providers - TOKEN_ENDPOINT configuration now required - Existing internal tokens invalid (tables dropped) Migration Impact: - Simpler codebase: -500 lines of code - Fewer database tables: -2 tables (tokens, authorization_codes) - More secure: External providers handle token security - More maintainable: Less authentication code to maintain Standards Compliance: - W3C IndieAuth specification - OAuth 2.0 Bearer token authentication - IndieWeb principle: delegate to external services Related: - ADR-030: IndieAuth Provider Removal Strategy - ADR-050: Remove Custom IndieAuth Server - Migration 003: Remove code_verifier from auth_state - Migration 004: Drop tokens and authorization_codes tables 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
7.3 KiB
IndieAuth Implementation Questions - Answered
Quick Reference
All architectural questions have been answered. This document provides the concrete guidance needed for implementation.
Questions & Answers
✅ Q1: External Token Endpoint Response Format
Answer: Follow the IndieAuth spec exactly (W3C TR).
Expected Response:
{
"me": "https://user.example.net/",
"client_id": "https://app.example.com/",
"scope": "create update delete"
}
Error Responses: HTTP 400, 401, or 403 for invalid tokens.
✅ Q2: HTML Discovery Headers
Answer: These are links users add to THEIR websites, not StarPunk.
User's HTML (on their personal domain):
<link rel="authorization_endpoint" href="https://indielogin.com/auth">
<link rel="token_endpoint" href="https://tokens.indieauth.com/token">
<link rel="micropub" href="https://your-starpunk.example.com/api/micropub">
StarPunk's Role: Discover these endpoints from the user's URL, don't generate them.
✅ Q3: Migration Strategy
Architectural Decision: Keep migration 002, document it as future-use.
Action Items:
- Keep the migration file as-is
- Add comment: "Tables created for future V2 internal provider support"
- Don't use these tables in V1 (external verification only)
- No impact on existing production databases
Rationale: Empty tables cause no harm, avoid migration complexity later.
✅ Q4: Error Handling
Answer: Show clear, informative error messages.
Error Messages:
- Auth server down: "Authorization server is unreachable. Please try again later."
- Invalid token: "Access token is invalid or expired. Please re-authorize."
- Network error: "Cannot connect to authorization server."
HTTP Status Codes:
- 401: No token provided
- 403: Invalid/expired token
- 503: Auth server unreachable
✅ Q5: Cache Revocation Delay
Architectural Decision: Use 5-minute cache with configuration options.
Implementation:
# Default: 5-minute cache
MICROPUB_TOKEN_CACHE_TTL=300
MICROPUB_TOKEN_CACHE_ENABLED=true
# High security: disable cache
MICROPUB_TOKEN_CACHE_ENABLED=false
Security Notes:
- SHA256 hash tokens before caching
- Memory-only cache (not persisted)
- Document 5-minute delay in security guide
- Allow disabling for high-security needs
Implementation Checklist
Immediate Actions
-
Remove Internal Provider Code:
- Delete
/auth/authorizeendpoint - Delete
/auth/tokenendpoint - Remove token issuance logic
- Remove authorization code generation
- Delete
-
Implement External Verification:
# Core verification function def verify_micropub_token(bearer_token, expected_me): # 1. Check cache (if enabled) # 2. Discover token endpoint from expected_me # 3. Verify with external endpoint # 4. Cache result (if enabled) # 5. Return validation result -
Add Configuration:
# Required ADMIN_ME=https://user.example.com # Optional (with defaults) MICROPUB_TOKEN_CACHE_ENABLED=true MICROPUB_TOKEN_CACHE_TTL=300 -
Update Error Handling:
try: response = httpx.get(endpoint, timeout=5.0) except httpx.TimeoutError: return error(503, "Authorization server is unreachable")
Code Examples
Token Verification
def verify_token(bearer_token: str, token_endpoint: str, expected_me: str) -> Optional[dict]:
"""Verify token with external endpoint"""
try:
response = httpx.get(
token_endpoint,
headers={'Authorization': f'Bearer {bearer_token}'},
timeout=5.0
)
if response.status_code == 200:
data = response.json()
if data.get('me') == expected_me and 'create' in data.get('scope', ''):
return data
return None
except httpx.TimeoutError:
raise TokenEndpointError("Authorization server is unreachable")
Endpoint Discovery
def discover_token_endpoint(me_url: str) -> str:
"""Discover token endpoint from user's URL"""
response = httpx.get(me_url)
# 1. Check HTTP Link header
if link := parse_link_header(response.headers.get('Link'), 'token_endpoint'):
return urljoin(me_url, link)
# 2. Check HTML <link> tags
if 'text/html' in response.headers.get('content-type', ''):
if link := parse_html_link(response.text, 'token_endpoint'):
return urljoin(me_url, link)
raise DiscoveryError(f"No token endpoint found at {me_url}")
Micropub Endpoint
@app.route('/api/micropub', methods=['POST'])
def micropub_endpoint():
# Extract token
auth = request.headers.get('Authorization', '')
if not auth.startswith('Bearer '):
return {'error': 'unauthorized'}, 401
token = auth[7:] # Remove "Bearer "
# Verify token
try:
token_info = verify_micropub_token(token, app.config['ADMIN_ME'])
if not token_info:
return {'error': 'forbidden'}, 403
except TokenEndpointError as e:
return {'error': 'temporarily_unavailable', 'error_description': str(e)}, 503
# Process Micropub request
# ... create note ...
return '', 201, {'Location': note_url}
Testing Guide
Manual Testing
- Configure your domain with IndieAuth links
- Set ADMIN_ME in StarPunk config
- Use Quill (https://quill.p3k.io) to test posting
- Verify token caching works (check logs)
- Test with auth server down (block network)
Automated Tests
def test_token_verification():
# Mock external token endpoint
with responses.RequestsMock() as rsps:
rsps.add(responses.GET, 'https://tokens.example.com/token',
json={'me': 'https://user.com', 'scope': 'create'})
result = verify_token('test-token', 'https://tokens.example.com/token', 'https://user.com')
assert result['me'] == 'https://user.com'
def test_auth_server_unreachable():
# Mock timeout
with pytest.raises(TokenEndpointError, match="unreachable"):
verify_token('test-token', 'https://timeout.example.com/token', 'https://user.com')
User Documentation Template
For Users: Setting Up IndieAuth
-
Add to your website's HTML:
<link rel="authorization_endpoint" href="https://indielogin.com/auth"> <link rel="token_endpoint" href="https://tokens.indieauth.com/token"> <link rel="micropub" href="[YOUR-STARPUNK-URL]/api/micropub"> -
Configure StarPunk:
ADMIN_ME=https://your-website.com -
Test with a Micropub client:
- Visit https://quill.p3k.io
- Enter your website URL
- Authorize and post!
Summary
All architectural questions have been answered:
- Token Format: Follow IndieAuth spec exactly
- HTML Headers: Users configure their own domains
- Migration: Keep tables for future use
- Errors: Clear messages about connectivity
- Cache: 5-minute TTL with disable option
The implementation path is clear: remove internal provider code, implement external verification with caching, and provide good error messages. This aligns with StarPunk's philosophy of minimal code and IndieWeb principles.
Ready for Implementation: All questions answered, examples provided, architecture documented.