Skip to main content

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

ConcernImplementation
User RegistrationSelf-service registration with email verification
LoginUsername/password with optional MFA (TOTP, WebAuthn)
Social LoginGitHub and Google identity providers
Token IssuanceJWT access tokens (15 min TTL) and refresh tokens (7 day TTL)
Session ManagementServer-side sessions with configurable idle and max lifetimes
Account ManagementPassword 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:

QuestionSpiceDB CheckResultReason
Can alice scan example.com?CheckPermission(user:alice, scan, domain:example.com)ALLOWalice is owner of acme, which owns example.com
Can bob scan example.com?CheckPermission(user:bob, scan, domain:example.com)ALLOWbob is member of acme, which owns example.com
Can bob delete example.com?CheckPermission(user:bob, delete, domain:example.com)DENYdelete requires manage (owner or admin), bob is only a member
Can bob cancel scan-001?CheckPermission(user:bob, cancel, scan_job:scan-001)ALLOWbob is the initiator of scan-001
Can alice cancel scan-001?CheckPermission(user:alice, cancel, scan_job:scan-001)ALLOWalice 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.