Add scoped tokens, L2-side revocation, and security docs

Security improvements:
- Tokens now include optional l1_server claim for scoping
- /auth/verify checks token scope matches requesting L1
- L2 maintains revoked_tokens table - even if L1 ignores revoke, token fails
- Logout revokes token in L2 db before notifying L1s
- /renderers/attach creates scoped tokens (not embedded in HTML)
- Add get_token_claims() to auth.py

Database:
- Add revoked_tokens table with token_hash, username, expires_at
- Add db functions: revoke_token, is_token_revoked, cleanup_expired_revocations

Documentation:
- Document security features in README

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gilesb
2026-01-09 18:21:13 +00:00
parent 4351c97ce0
commit 5ebfdac887
4 changed files with 149 additions and 23 deletions

View File

@@ -110,14 +110,29 @@ export L1_SERVERS=https://celery-artdag.rose-ash.com,https://renderer2.example.c
```
When a user attaches to an L1 server:
1. They're redirected to the L1's `/auth` endpoint with their auth token
2. L1 calls back to L2's `/auth/verify` endpoint to validate the token
3. L1 sets its own local cookie, logging the user in
4. Their attachment is recorded in the `user_renderers` table
1. L2 creates a **scoped token** that only works for that specific L1
2. User is redirected to the L1's `/auth` endpoint with the scoped token
3. L1 calls back to L2's `/auth/verify` endpoint to validate the token
4. L2 verifies the token scope matches the requesting L1
5. L1 sets its own local cookie, logging the user in
6. The attachment is recorded in L2's `user_renderers` table
**No shared secrets required**: L1 servers verify tokens by calling L2's `/auth/verify` endpoint. This allows any L1 provider to federate with L2 without needing the JWT secret.
### Security Features
**Authorization**: L1 servers must identify themselves when calling `/auth/verify` by passing their URL. Only servers listed in `L1_SERVERS` are authorized to verify tokens.
**No shared secrets**: L1 servers verify tokens by calling L2's `/auth/verify` endpoint. Any L1 provider can federate without the JWT secret.
**L1 authorization**: Only servers listed in `L1_SERVERS` can verify tokens. L1 must identify itself in verify requests.
**Scoped tokens**: Tokens issued for attachment contain an `l1_server` claim. A token scoped to L1-A cannot be used on L1-B, preventing malicious L1s from stealing tokens for use elsewhere.
**Federated logout**: When a user logs out of L2:
1. L2 revokes the token in its database (`revoked_tokens` table)
2. L2 calls `/auth/revoke` on all attached L1s to revoke their local copies
3. All attachments are removed from `user_renderers`
Even if a malicious L1 ignores the revoke call, the token will fail verification at L2 because it's in the revocation table.
**Token revocation on L1**: L1 servers maintain their own Redis-based revocation list and check it on every authenticated request.
Users can manage attachments at `/renderers`.