12 KiB
ADR-005: IndieLogin Authentication Integration
Status
Accepted
Context
The user has explicitly required external IndieLogin authentication via indielogin.com for V1. This is different from implementing a full IndieAuth server (which CLAUDE.MD mentions). The distinction is important:
- IndieAuth Server: Host your own authentication endpoint (complex)
- IndieLogin Service: Use indielogin.com as an external authentication provider (simple)
The user wants the simpler approach: delegate authentication to indielogin.com using their API (https://indielogin.com/api).
IndieLogin.com is a service that:
- Handles the OAuth 2.0 / IndieAuth flow
- Verifies user identity via their website
- Returns authenticated identity to our application
- Supports multiple authentication methods (RelMeAuth, email, etc.)
Decision
Use IndieLogin.com as External Authentication Provider
Authentication Flow: OAuth 2.0 Authorization Code flow via indielogin.com API Endpoint: https://indielogin.com/auth Token Validation: Server-side session tokens (not IndieAuth tokens) User Identity: URL (me parameter) verified by indielogin.com
Architecture
User Browser → StarPunk → indielogin.com → User's Website
↑ ↓
└──────────────────────────────┘
(Authenticated session)
Authentication Flow
1. Login Initiation
User clicks "Login"
↓
StarPunk generates state token (CSRF protection)
↓
Redirect to: https://indielogin.com/auth?
- me={user_website}
- client_id={starpunk_url}
- redirect_uri={starpunk_url}/auth/callback
- state={random_token}
2. IndieLogin Processing
indielogin.com verifies user identity:
- Checks for rel="me" links on user's website
- Or sends email verification
- Or uses other IndieAuth methods
↓
User authenticates via their chosen method
↓
indielogin.com redirects back to StarPunk
3. Callback Verification
indielogin.com → StarPunk callback with:
- code={authorization_code}
- state={original_state}
↓
StarPunk verifies state matches
↓
StarPunk exchanges code for verified identity:
POST https://indielogin.com/auth
- code={authorization_code}
- client_id={starpunk_url}
- redirect_uri={starpunk_url}/auth/callback
↓
indielogin.com responds with:
{ "me": "https://user-website.com" }
↓
StarPunk creates authenticated session
4. Session Management
StarPunk stores session token in cookie
↓
Session token maps to authenticated user URL
↓
Admin routes check for valid session
Implementation Requirements
Configuration Variables
SITE_URL=https://starpunk.example.com
ADMIN_ME=https://your-website.com
SESSION_SECRET=random_secret_key
Database Schema Addition
-- Add to existing schema
CREATE TABLE sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_token TEXT UNIQUE NOT NULL,
me TEXT NOT NULL, -- Authenticated user URL
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP NOT NULL,
last_used_at TIMESTAMP
);
CREATE INDEX idx_sessions_token ON sessions(session_token);
CREATE INDEX idx_sessions_expires ON sessions(expires_at);
CREATE TABLE auth_state (
state TEXT PRIMARY KEY,
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP NOT NULL -- Short-lived (5 minutes)
);
HTTP Client for API Calls
Use httpx (already selected in ADR-002) for:
- POST to https://indielogin.com/auth to exchange code
- Verify response contains valid "me" URL
- Handle network errors gracefully
Routes Required
GET /admin/login - Display login form
POST /admin/login - Initiate IndieLogin flow
GET /auth/callback - Handle IndieLogin redirect
POST /admin/logout - Destroy session
Login Flow Implementation
Step 1: Login Form
# /admin/login (GET)
# Display simple form asking for user's website URL
# Form submits to POST /admin/login with "me" parameter
Step 2: Initiate Authentication
# /admin/login (POST)
def initiate_login(me_url):
# Validate me_url format
if not is_valid_url(me_url):
return error("Invalid URL")
# Generate and store state token
state = generate_random_token()
store_state(state, expires_in_minutes=5)
# Build IndieLogin authorization URL
params = {
'me': me_url,
'client_id': SITE_URL,
'redirect_uri': f"{SITE_URL}/auth/callback",
'state': state
}
auth_url = f"https://indielogin.com/auth?{urlencode(params)}"
# Redirect user to IndieLogin
return redirect(auth_url)
Step 3: Handle Callback
# /auth/callback (GET)
def handle_callback(code, state):
# Verify state token (CSRF protection)
if not verify_state(state):
return error("Invalid state")
# Exchange code for verified identity
response = httpx.post('https://indielogin.com/auth', data={
'code': code,
'client_id': SITE_URL,
'redirect_uri': f"{SITE_URL}/auth/callback"
})
if response.status_code != 200:
return error("Authentication failed")
data = response.json()
me = data.get('me')
# Verify this is the authorized admin
if me != ADMIN_ME:
return error("Unauthorized user")
# Create session
session_token = generate_random_token()
create_session(session_token, me, expires_in_days=30)
# Set session cookie
set_cookie('session', session_token, httponly=True, secure=True)
# Redirect to admin dashboard
return redirect('/admin')
Step 4: Session Validation
# Decorator for protected routes
def require_auth(f):
def wrapper(*args, **kwargs):
session_token = request.cookies.get('session')
if not session_token:
return redirect('/admin/login')
session = get_session(session_token)
if not session or session.expired:
return redirect('/admin/login')
# Update last_used_at
update_session_activity(session_token)
# Store user info in request context
g.user_me = session.me
return f(*args, **kwargs)
return wrapper
# Usage
@app.route('/admin')
@require_auth
def admin_dashboard():
return render_template('admin/dashboard.html')
Rationale
Why IndieLogin.com Instead of Self-Hosted IndieAuth?
Simplicity Score: 10/10 (IndieLogin) vs 4/10 (Self-hosted)
- IndieLogin.com handles all complexity of:
- Discovering user's auth endpoints
- Verifying user identity
- Supporting multiple auth methods (RelMeAuth, email, etc.)
- PKCE implementation
- Self-hosted would require implementing full IndieAuth spec (complex)
Fitness Score: 10/10
- Perfect for single-user system
- User controls their identity via their own website
- No password management needed
- Aligns with IndieWeb principles
Maintenance Score: 10/10
- indielogin.com is maintained by IndieWeb community
- No auth code to maintain ourselves
- Security updates handled externally
- Well-tested service
Standards Compliance: Pass
- Uses OAuth 2.0 / IndieAuth standards
- Compatible with IndieWeb ecosystem
- User identity is their URL (IndieWeb principle)
Why Session Cookies Instead of Access Tokens?
For admin interface (not Micropub):
- Simpler: Standard web session pattern
- Secure: HttpOnly cookies prevent XSS
- Appropriate: Admin is human using browser, not API client
- Note: Micropub will still use access tokens (separate ADR needed)
Consequences
Positive
- Extremely simple implementation (< 100 lines of code)
- No authentication code to maintain
- Secure by default (delegated to trusted service)
- True IndieWeb authentication (user owns identity)
- No passwords to manage
- Works immediately without setup
- Community-maintained service
Negative
- Dependency on external service (indielogin.com)
- Requires internet connection to authenticate
- Single point of failure for login (mitigated: session stays valid)
- User must have their own website/URL
Mitigation
- Sessions last 30 days, so brief indielogin.com outages don't lock out user
- Document fallback: edit database to create session manually if needed
- IndieLogin.com is stable, community-run service with good uptime
- For V2: Consider optional email fallback or self-hosted IndieAuth
Security Considerations
State Token (CSRF Protection)
- Generate cryptographically random state token
- Store in database with short expiry (5 minutes)
- Verify state matches on callback
- Delete state after use (single-use tokens)
Session Token Security
- Generate with secrets.token_urlsafe(32) or similar
- Store hash in database (not plaintext)
- Mark cookies as HttpOnly and Secure
- Set SameSite=Lax for CSRF protection
- Implement session expiry (30 days)
- Support manual logout (session deletion)
Identity Verification
- Only allow ADMIN_ME URL to authenticate
- Verify "me" URL from indielogin.com exactly matches config
- Reject any other authenticated users
- Log authentication attempts
Network Security
- Use HTTPS for all communication
- Verify SSL certificates on httpx requests
- Handle network timeouts gracefully
- Log authentication failures
Testing Strategy
Unit Tests
- State token generation and validation
- Session creation and expiry
- URL validation
- Cookie handling
Integration Tests
- Mock indielogin.com API responses
- Test full authentication flow
- Test session expiry
- Test unauthorized user rejection
- Test CSRF protection (invalid state)
Manual Testing
- Authenticate with real indielogin.com
- Verify session persistence
- Test logout functionality
- Test session expiry
- Test with wrong "me" URL
Alternatives Considered
Self-Hosted IndieAuth Server (Rejected)
- Complexity: Must implement full IndieAuth spec
- Maintenance: Security updates, endpoint discovery, token generation
- Verdict: Too complex for V1, violates simplicity principle
Password Authentication (Rejected)
- Security: Must hash passwords, handle resets, prevent brute force
- IndieWeb: Violates IndieWeb principle of URL-based identity
- Verdict: Not aligned with project goals
OAuth via GitHub/Google (Rejected)
- Simplicity: Easy to implement
- IndieWeb: Not IndieWeb-compatible, user doesn't own identity
- Verdict: Violates IndieWeb requirements
Email Magic Links (Rejected)
- Simplicity: Requires email sending infrastructure
- IndieWeb: Not standard IndieWeb authentication
- Verdict: Deferred to V2 as fallback option
Multi-User IndieAuth (Rejected for V1)
- Scope: V1 is explicitly single-user
- Complexity: Would require user management
- Verdict: Out of scope, defer to V2
Implementation Checklist
- Add SESSION_SECRET and ADMIN_ME to configuration
- Create sessions and auth_state database tables
- Implement state token generation and storage
- Create login form template
- Implement /admin/login routes (GET and POST)
- Implement /auth/callback route
- Implement session creation and validation
- Create require_auth decorator
- Implement logout functionality
- Set secure cookie parameters
- Add authentication error handling
- Write unit tests for auth flow
- Write integration tests with mocked indielogin.com
- Test with real indielogin.com
- Document setup process for users
Configuration Example
# .env file
SITE_URL=https://starpunk.example.com
ADMIN_ME=https://your-website.com
SESSION_SECRET=your-random-secret-key-here
User Setup Documentation
- Deploy StarPunk to your server at
https://starpunk.example.com - Configure
ADMIN_MEto your personal website URL - Visit
/admin/login - Enter your website URL (must match ADMIN_ME)
- indielogin.com will verify your identity
- Authenticate via your chosen method
- Redirected back to StarPunk admin interface
References
- IndieLogin.com: https://indielogin.com/
- IndieLogin API Documentation: https://indielogin.com/api
- IndieAuth Specification: https://indieauth.spec.indieweb.org/
- OAuth 2.0 Spec: https://oauth.net/2/
- Web Authentication Best Practices: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html