Authentication & Authorization
RedactedWorld uses Keycloak for authentication (identity) and SpiceDB for authorization (permissions). This separation allows the identity provider to focus on secure credential management while the authorization layer handles fine-grained, relationship-based access control.
Authentication -- Keycloak
Keycloak runs at keycloak.redactedworld.com and serves as the central OpenID Connect (OIDC) provider for the entire platform.
Responsibilities
| Concern | Implementation |
|---|---|
| User Registration | Self-service registration with email verification |
| Login | Username/password with optional MFA (TOTP, WebAuthn) |
| Social Login | GitHub and Google identity providers |
| Token Issuance | JWT access tokens (15 min TTL) and refresh tokens (7 day TTL) |
| Session Management | Server-side sessions with configurable idle and max lifetimes |
| Account Management | Password reset, email change, MFA enrollment via Keycloak Account Console |
OIDC Flow
Keycloak Realm Configuration
The platform uses a single realm called redactedworld with the following settings:
- Client:
redactedworld-app(public client, PKCE required) - Token lifespan: Access token 15 minutes, refresh token 7 days
- Login theme: Custom branded theme matching the platform design
- Required actions: Verify email on registration
- Brute force protection: Enabled (locks account after 5 failed attempts for 15 minutes)
Authorization -- SpiceDB
While Keycloak answers "Who is this user?", SpiceDB answers "Is this user allowed to do this action on this resource?"
SpiceDB is a Zanzibar-inspired authorization database that stores relationships between subjects and objects, then evaluates permissions by traversing the relationship graph.
Why Not RBAC?
Traditional role-based access control (RBAC) assigns roles like admin or member and checks them on each request. This breaks down when you need:
- Resource-level permissions: "User A can scan domain X but not domain Y"
- Inherited permissions: "Members of Org Z can scan all domains owned by Org Z"
- Cross-cutting policies: "The user who initiated a scan can cancel it, even if they are only a member"
SpiceDB handles all of these naturally through its relationship graph.
Example Relationships
Here are example relationships that would exist in a running system:
// User alice is the owner of organization acme
organization:acme#owner@user:alice
// User bob is a member of organization acme
organization:acme#member@user:bob
// Domain example.com belongs to organization acme
domain:example.com#organization@organization:acme
// Domain example.com was verified by alice
domain:example.com#verified_by@user:alice
// Scan job scan-001 targets domain example.com
scan_job:scan-001#domain@domain:example.com
// Scan job scan-001 was initiated by bob
scan_job:scan-001#initiated_by@user:bob
Permission Evaluation
Given the relationships above, SpiceDB can answer these questions:
| Question | SpiceDB Check | Result | Reason |
|---|---|---|---|
| Can alice scan example.com? | CheckPermission(user:alice, scan, domain:example.com) | ALLOW | alice is owner of acme, which owns example.com |
| Can bob scan example.com? | CheckPermission(user:bob, scan, domain:example.com) | ALLOW | bob is member of acme, which owns example.com |
| Can bob delete example.com? | CheckPermission(user:bob, delete, domain:example.com) | DENY | delete requires manage (owner or admin), bob is only a member |
| Can bob cancel scan-001? | CheckPermission(user:bob, cancel, scan_job:scan-001) | ALLOW | bob is the initiator of scan-001 |
| Can alice cancel scan-001? | CheckPermission(user:alice, cancel, scan_job:scan-001) | ALLOW | alice has manage permission on the domain via org ownership |
Integration Points
The auth-service is the primary writer to SpiceDB -- it writes relationships whenever a user joins an organization, an organization is created, or roles change. The domain-service writes relationships when a domain is verified. The scan-service writes relationships when a scan job is created.
The API Gateway is the primary reader -- it checks permissions on every incoming request before forwarding to downstream services.