Files
Gondulf/docs/designs/response-type-fix.md
Phil Skentelbery 9135edfe84 fix(auth): require email authentication every login
CRITICAL SECURITY FIX:
- Email code required EVERY login (authentication, not verification)
- DNS TXT check cached separately (domain verification)
- New auth_sessions table for per-login state
- Codes hashed with SHA-256, constant-time comparison
- Max 3 attempts, 10-minute session expiry
- OAuth params stored server-side (security improvement)

New files:
- services/auth_session.py
- migrations 004, 005
- ADR-010: domain verification vs user authentication

312 tests passing, 86.21% coverage

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-22 15:16:26 -07:00

6.3 KiB

Fix: Response Type Parameter Default Handling

Problem Statement

The current authorization endpoint incorrectly requires the response_type parameter for all requests. According to the W3C IndieAuth specification:

  • Section 5.2: When response_type is omitted in an authentication request, the authorization endpoint MUST default to id
  • Section 6.2.1: The response_type=code is required for authorization (access token) requests

Currently, the endpoint returns an error when response_type is missing, instead of defaulting to id.

Design Overview

Modify the authorization endpoint to:

  1. Accept response_type as optional
  2. Default to id when omitted
  3. Support both id (authentication) and code (authorization) flows
  4. Return appropriate errors for invalid values

Implementation Changes

1. Response Type Validation Logic

Location: /src/gondulf/routers/authorization.py lines 111-119

Current implementation:

# Validate response_type
if response_type != "code":
    error_params = {
        "error": "unsupported_response_type",
        "error_description": "Only response_type=code is supported",
        "state": state or ""
    }
    redirect_url = f"{redirect_uri}?{urlencode(error_params)}"
    return RedirectResponse(url=redirect_url, status_code=302)

New implementation:

# Validate response_type (defaults to 'id' per IndieAuth spec section 5.2)
if response_type is None:
    response_type = "id"  # Default per W3C spec

if response_type not in ["id", "code"]:
    error_params = {
        "error": "unsupported_response_type",
        "error_description": f"response_type '{response_type}' not supported. Must be 'id' or 'code'",
        "state": state or ""
    }
    redirect_url = f"{redirect_uri}?{urlencode(error_params)}"
    return RedirectResponse(url=redirect_url, status_code=302)

2. Flow-Specific Validation

The authentication flow (id) and authorization flow (code) have different requirements:

Authentication Flow (response_type=id)

  • PKCE is optional (not required)
  • Scope is not applicable
  • Returns only user profile URL

Authorization Flow (response_type=code)

  • PKCE is required (current behavior)
  • Scope is applicable
  • Returns authorization code for token exchange

Modified PKCE validation (lines 121-139):

# Validate PKCE (required only for authorization flow)
if response_type == "code":
    if not code_challenge:
        error_params = {
            "error": "invalid_request",
            "error_description": "code_challenge is required for authorization requests (PKCE)",
            "state": state or ""
        }
        redirect_url = f"{redirect_uri}?{urlencode(error_params)}"
        return RedirectResponse(url=redirect_url, status_code=302)

    # Validate code_challenge_method
    if code_challenge_method != "S256":
        error_params = {
            "error": "invalid_request",
            "error_description": "code_challenge_method must be S256",
            "state": state or ""
        }
        redirect_url = f"{redirect_uri}?{urlencode(error_params)}"
        return RedirectResponse(url=redirect_url, status_code=302)

3. Template Context Update

Pass the resolved response_type to the consent template (line 177-189):

return templates.TemplateResponse(
    "authorize.html",
    {
        "request": request,
        "client_id": normalized_client_id,
        "redirect_uri": redirect_uri,
        "response_type": response_type,  # Add this - resolved value
        "state": state or "",
        "code_challenge": code_challenge or "",  # Make optional
        "code_challenge_method": code_challenge_method or "",  # Make optional
        "scope": scope or "",
        "me": me,
        "client_metadata": client_metadata
    }
)

The consent handler needs to differentiate between authentication and authorization flows:

Location: /src/gondulf/routers/authorization.py lines 193-245

Add response_type parameter to the form submission and handle accordingly:

  1. Add response_type as a form field (line ~196)
  2. Process differently based on flow type
  3. For id flow: Return simpler response without creating full authorization code
  4. For code flow: Current behavior (create authorization code)

Test Requirements

New Test Cases

  1. Test missing response_type defaults to 'id'

    • Request without response_type parameter
    • Should NOT return error
    • Should render consent page
    • Form should have response_type=id
  2. Test explicit response_type=id accepted

    • Request with response_type=id
    • Should render consent page
    • PKCE parameters not required
  3. Test response_type=id without PKCE

    • Request with response_type=id and no PKCE
    • Should succeed (PKCE optional for authentication)
  4. Test response_type=code requires PKCE

    • Request with response_type=code without PKCE
    • Should redirect with error (current behavior)
  5. Test invalid response_type values

    • Request with response_type=token or other invalid values
    • Should redirect with error

Modified Test Cases

Update existing test in test_authorization_flow.py:

  • Line 115-126: test_invalid_response_type_redirects_with_error
    • Keep testing invalid values like "token"
    • Add new test for missing parameter (should NOT error)

Acceptance Criteria

  1. Missing response_type defaults to id (no error)
  2. response_type=id is accepted and processed
  3. response_type=code continues to work as before
  4. Invalid response_type values return appropriate error
  5. PKCE is optional for id flow
  6. PKCE remains required for code flow
  7. Error messages clearly indicate supported values
  8. All existing tests pass with modifications
  9. New tests cover all response_type scenarios

Security Considerations

  • No security degradation: Authentication flow (id) has fewer requirements by design
  • PKCE remains mandatory for authorization flow (code)
  • Invalid values still produce errors
  • State parameter continues to be preserved in all flows

Notes

This is a bug fix to bring the implementation into compliance with the W3C IndieAuth specification. The specification is explicit that response_type defaults to id when omitted, which enables simpler authentication-only flows.