IndieAuth Implementation ======================== Django-IndieWeb provides a complete IndieAuth implementation that supports both authentication (logging into sites) and authorization (granting permissions to apps). Overview -------- IndieAuth is a federated login protocol that enables users to sign in to websites using their own domain name. Django-IndieWeb implements all three IndieAuth endpoints: 1. **Authorization Endpoint** (``/indieweb/auth/``) - Handles user consent and generates auth codes 2. **Token Endpoint** (``/indieweb/token/``) - Exchanges auth codes for access tokens 3. **Authentication** - Verifies auth codes for login-only flows Authorization Flow with Consent Screen -------------------------------------- When a client application requests authorization with scopes (permissions), Django-IndieWeb displays a consent screen to the user. The consent screen shows: * The client application requesting access (client_id) * The user's identity URL (me) * Requested permissions/scopes (if any) * Approve and Deny buttons Example consent screen:: Authorization Request https://quill.p3k.io is requesting access to your site. Your identity URL: https://example.com Requested permissions: • create • update • delete [Approve] [Deny] Authentication vs Authorization ------------------------------- Django-IndieWeb supports two different IndieAuth flows: **Authentication Only (No Scopes)** Used when logging into websites with your domain. No consent screen is required since no permissions are granted:: GET /indieweb/auth/?me=https://example.com&client_id=https://site.com&redirect_uri=...&state=... **Authorization with Scopes** Used when granting permissions to apps (like Micropub clients). Shows consent screen:: GET /indieweb/auth/?me=https://example.com&client_id=https://app.com&redirect_uri=...&state=...&scope=create+update Common scopes include: * ``create`` - Create new posts * ``update`` - Edit existing posts * ``delete`` - Delete posts * ``media`` - Upload media files Scope strings are normalized before display and before storage: whitespace is collapsed, duplicate tokens are removed while preserving first-seen order, and an empty or whitespace-only value is treated as no scope. Unknown scope names are intentionally preserved. IndieAuth and Micropub scopes are extension-defined, and clients may request values such as ``profile``, ``media``, or site-specific scopes before django-indieweb implements matching resource-server behavior. Customizing the Consent Screen ------------------------------ The consent screen template can be customized by overriding ``indieweb/consent.html`` in your project: 1. Create the directory structure in your templates folder:: templates/ └── indieweb/ └── consent.html 2. Copy the default template as a starting point:: {% extends "base.html" %} {% block content %}
{% endblock %} Available template context variables: * ``client_id`` - The application requesting access * ``redirect_uri`` - Where to redirect after authorization * ``state`` - State parameter for CSRF protection * ``me`` - The user's identity URL * ``scope`` - Normalized space-separated list of requested scopes, or ``None`` when no scope was requested * ``scope_list`` - Python list of individual scopes * ``code_challenge`` - PKCE challenge from the authorization request, or ``None`` when no PKCE was sent. Custom templates that omit this hidden input will silently drop PKCE and will not interoperate with PKCE clients. * ``code_challenge_method`` - PKCE method (``S256`` or ``plain``) paired with ``code_challenge`` Security Considerations ----------------------- 1. **HTTPS Required**: Always use HTTPS in production for all IndieAuth endpoints 2. **Auth Code Timeout**: Auth codes expire after 60 seconds by default 3. **Token Expiration**: Access tokens expire after 24 hours by default; the Micropub endpoint rejects expired tokens with HTTP 401 4. **CSRF Protection**: The consent form includes Django's CSRF token 5. **User Authentication**: Users must be logged in to approve/deny requests 6. **PKCE (RFC 7636)**: The authorization endpoint accepts an optional ``code_challenge`` (43-128 characters from the unreserved set ``[A-Za-z0-9._~-]``) and ``code_challenge_method`` (``S256`` or ``plain``; defaults to ``plain`` when ``code_challenge`` is sent without a method, per RFC 7636 §4.3). Malformed challenges and unsupported methods are rejected with HTTP 400 *before* any authorization code is issued. When an auth code was issued with a ``code_challenge``, the token endpoint requires a matching ``code_verifier`` and rejects any mismatch, any missing verifier, any verifier submitted without a stored challenge, and any verifier outside the RFC length and character set with ``invalid_grant``. The S256 verification computes ``BASE64URL-WITHOUT-PADDING(SHA256(verifier))`` and compares it to the stored challenge in constant time. Auth codes issued before this change (no ``code_challenge`` stored) continue to be redeemable without a ``code_verifier``, preserving backwards compatibility for legacy clients. 7. **client_id Validation**: Submitted ``client_id`` values must be syntactically valid URLs using the ``http`` or ``https`` scheme. They must not contain a fragment delimiter (``#``) at all, even with no fragment content, and must not include userinfo (``user:pass@``). The authorization endpoint (GET, consent POST, and code-verification POST) rejects malformed values with HTTP 400 *before* creating an authorization code: plain-text body ``invalid client_id`` for structural failures and ``invalid_client`` for validator failures. The token endpoint rejects malformed submissions with HTTP 400 ``invalid_request`` and content type ``application/x-www-form-urlencoded`` (matching the existing missing- ``code`` case). Operators can additionally restrict which clients are allowed by setting ``INDIEWEB_CLIENT_ID_VALIDATOR`` (see :doc:`configuration`); the configured callable runs at every authorization/token path and on every Micropub request, so revoking a client takes effect immediately for previously-issued tokens. A misconfigured validator (the dotted path fails to import or the callable raises) fails closed at every call site. Stored ``client_id`` values are not re-validated *structurally* on use, matching the ``redirect_uri`` rule; the configured validator, however, IS re-applied on use, so pre-existing tokens whose ``client_id`` no longer satisfies operator policy are rejected with HTTP 403 ``invalid_client`` on the Micropub endpoint. 8. **redirect_uri Validation**: Submitted ``redirect_uri`` values must be syntactically valid URLs using the ``http`` or ``https`` scheme. They must not contain a fragment delimiter (``#``) at all, even with no fragment content, and must not include userinfo (``user:pass@``). The authorization endpoint rejects malformed values with HTTP 400 *before* creating an authorization code; the token endpoint rejects malformed submissions with ``invalid_grant``. When comparing the value submitted at the token endpoint with the value stored alongside the authorization code, the scheme and host are compared case-insensitively while the path and query are compared verbatim. ``redirect_uri`` values that already contain a query (e.g. ``?next=/x``) are preserved when ``code`` and ``state`` are appended. 9. **Scope Issuance Semantics**: The authorization endpoint normalizes scope strings before showing them on the consent screen and before storing them on the ``Auth`` row. Normalization splits on whitespace, de-duplicates while preserving first-seen order, and joins with single spaces; whitespace-only values become no scope. Unknown scopes are accepted and preserved after normalization because IndieAuth/Micropub scopes are extension-defined. The token endpoint issues the exact normalized scope stored with the auth code. If a token exchange includes a ``scope`` parameter, that submitted value is normalized and must match the stored auth-code scope exactly. A mismatch returns HTTP 400 ``invalid_grant`` with content type ``application/x-www-form-urlencoded`` and does not create or reissue a token. Omitting ``scope`` on token exchange continues to issue the stored auth-code scope. An explicitly empty ``scope=`` parameter normalizes to no scope, so it only succeeds when the auth code was issued with no scope. 10. **Per-Operation Scope Enforcement**: The Micropub resource server enforces scopes per operation rather than treating ``create`` as a master scope. The W3C Micropub Recommendation (`§5 Scope