Files
StarPunk/docs/architecture/indieauth-questions-answered.md
Phil Skentelbery a3bac86647 feat: Complete IndieAuth server removal (Phases 2-4)
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>
2025-11-24 17:23:46 -07:00

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:

  1. Keep the migration file as-is
  2. Add comment: "Tables created for future V2 internal provider support"
  3. Don't use these tables in V1 (external verification only)
  4. 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

  1. Remove Internal Provider Code:

    • Delete /auth/authorize endpoint
    • Delete /auth/token endpoint
    • Remove token issuance logic
    • Remove authorization code generation
  2. 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
    
  3. Add Configuration:

    # Required
    ADMIN_ME=https://user.example.com
    
    # Optional (with defaults)
    MICROPUB_TOKEN_CACHE_ENABLED=true
    MICROPUB_TOKEN_CACHE_TTL=300
    
  4. 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

  1. Configure your domain with IndieAuth links
  2. Set ADMIN_ME in StarPunk config
  3. Use Quill (https://quill.p3k.io) to test posting
  4. Verify token caching works (check logs)
  5. 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

  1. 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">
    
  2. Configure StarPunk:

    ADMIN_ME=https://your-website.com
    
  3. Test with a Micropub client:


Summary

All architectural questions have been answered:

  1. Token Format: Follow IndieAuth spec exactly
  2. HTML Headers: Users configure their own domains
  3. Migration: Keep tables for future use
  4. Errors: Clear messages about connectivity
  5. 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.