Skip to content

Authentication & User Management

Overview

Authentication and user management form the foundational security layer of the Progressive World-Building Platform. Every API request -- whether creating a timeline, inviting a collaborator, or viewing a character sheet -- must pass through authentication to establish the identity of the caller, and that identity is then used across the entire system for authorization decisions.

This feature handles user registration, login, session management via JWT tokens, password resets, profile management, and the critical intersection between authentication and the invitation system. When a Game Master invites a player by email, the platform must gracefully handle both cases: the invitee already has an account, or they need to register first. Authentication is the gateway that makes this possible.

Because the auth provider is a pending team decision (ASP.NET Identity, Auth0/Clerk, or Firebase Auth), this document describes the authentication contract -- the behaviors and interfaces the rest of the system depends on -- rather than a specific implementation. Regardless of provider choice, the application maintains its own user records, role-based authorization logic, and permission checks.

Goals

  • Provide secure user registration with email verification
  • Issue and manage short-lived access tokens with long-lived refresh tokens
  • Support password reset via email-based token flow
  • Establish a stable internal user identity (UUIDv7) that the rest of the system references
  • Bridge external auth provider identities to internal user records
  • Enable seamless invitation acceptance for both new and existing users
  • Expose a clean ICurrentUserService abstraction so all other features can access auth context without coupling to the auth provider
  • Support future multi-provider authentication (e.g., adding OAuth providers later)

User Stories

As a Game Master, I want to register an account so I can create and manage my campaign worlds.

As a Game Master, I want to invite players by email so they can join my project, whether or not they already have an account.

As a Writer, I want to log in and receive a token so I can access my projects across sessions without re-entering credentials constantly.

As a Player, I want to click an invitation link, register if needed, and land directly in the project I was invited to.

As a User, I want to reset my password if I forget it, without losing access to my projects or roles.

As a User, I want to update my display name and profile details so collaborators can identify me.

As a Developer, I want a consistent auth context available in every request so I can check permissions without worrying about which auth provider is behind it.

Functional Description

User Registration

Registration creates a new user account and an internal user record. The internal record is the source of truth for the platform -- all project memberships, content ownership, and permissions reference the internal user ID (UUIDv7).

Registration fields:

  • Email address (required, unique across platform)
  • Password (required, minimum 8 characters, complexity rules enforced by provider)
  • Display name (required, 2-50 characters)

Registration behavior:

  1. Validate input (email format, password strength, display name length)
  2. Create the account with the auth provider (or internal identity system)
  3. Create an internal User record with a new UUIDv7 and link to the external provider identity
  4. Send a verification email with a time-limited token
  5. Return a success response (user must verify email before logging in)
  6. If the registration email matches a pending project invitation, do NOT auto-accept -- the user must explicitly accept after verifying and logging in

Email verification:

  • Verification tokens expire after 24 hours
  • Users can request a new verification email (rate-limited to 3 per hour)
  • Unverified accounts cannot log in
  • Unverified accounts are automatically purged after 7 days

Login & Session Management

Login authenticates a user and issues a token pair (access token + refresh token) that the client uses for subsequent API requests.

Login fields:

  • Email address
  • Password

Login behavior:

  1. Validate credentials against the auth provider
  2. Reject if email is not verified
  3. Reject if account is locked (too many failed attempts)
  4. Issue an access token (JWT, short-lived) and a refresh token (opaque, long-lived)
  5. Record the login event (timestamp, IP, user agent) for audit purposes
  6. Return the token pair and basic user profile

Token characteristics:

TokenFormatLifetimeStorage
Access TokenJWT15 minutesClient memory (not localStorage)
Refresh TokenOpaque string7 daysHTTP-only secure cookie

JWT claims:

  • sub -- internal user ID (UUIDv7)
  • email -- user email
  • name -- display name
  • iat -- issued at
  • exp -- expiration
  • jti -- unique token ID (for revocation checks)

Account lockout:

  • After 5 consecutive failed login attempts, lock the account for 15 minutes
  • After 10 cumulative failed attempts in 24 hours, lock for 1 hour
  • Lockout resets on successful login or password reset

Token Refresh

The refresh mechanism lets clients obtain new access tokens without re-entering credentials. This is the primary mechanism for maintaining sessions.

Refresh behavior:

  1. Client sends the refresh token (from HTTP-only cookie)
  2. Validate the refresh token (not expired, not revoked, matches a known session)
  3. Issue a new access token and optionally rotate the refresh token
  4. If the refresh token is expired or revoked, return 401 -- client must re-authenticate

Refresh token rotation:

  • Each refresh issues a new refresh token and invalidates the old one
  • If a previously-invalidated refresh token is used (replay detection), revoke the entire token family and force re-authentication
  • This limits the damage window if a refresh token is compromised

Password Reset

Password reset allows users to regain access via a verified email address.

Reset request:

  1. User provides their email address
  2. If the email exists, send a reset link with a time-limited token
  3. Always return a generic success message (do not reveal whether the email exists)
  4. Reset tokens expire after 1 hour
  5. Rate limit: 3 reset requests per email per hour

Reset completion:

  1. User clicks the link and provides a new password
  2. Validate the reset token (not expired, not already used, matches email)
  3. Update the password with the auth provider
  4. Invalidate all existing refresh tokens for the user (force re-login on all devices)
  5. Send a confirmation email that the password was changed
  6. Return success -- user must log in with the new password

Invitation Acceptance Flow

This is the critical integration point between authentication and the project membership system (see Project Management). When a project member invites someone by email, the invitation flow varies based on whether the invitee already has an account.

Invitation link structure:

  • Links contain an invitation token that encodes: project ID, invited email, inviter ID, expiry
  • Links point to a frontend route that determines the correct flow

Existing user flow:

  1. User clicks invitation link
  2. Frontend detects the user is already logged in (or prompts login)
  3. Frontend calls the invitation acceptance endpoint with the invitation token
  4. Backend validates: token not expired, email matches logged-in user, invitation still pending
  5. Backend updates the ProjectMember record: status changes from pending to accepted, joinedAt is set
  6. User is redirected to the project

New user flow:

  1. User clicks invitation link
  2. Frontend detects no active session and shows registration form, preserving the invitation token
  3. User registers (standard registration flow)
  4. After email verification and first login, the frontend automatically calls the invitation acceptance endpoint
  5. Same acceptance logic as existing user flow
  6. User is redirected to the project

Edge cases for invitations:

  • If the invitation has expired, show a clear message and suggest contacting the project owner
  • If the invitation was already accepted, redirect to the project
  • If the invitation was declined, show a message that the invitation is no longer valid
  • If the email on the invitation does not match the logged-in user, reject with a clear error

Account Management

Users can update their profile information after authentication.

Updatable fields:

  • Display name (2-50 characters)
  • Avatar URL (optional, validated URL format)
  • Bio (optional, max 500 characters)

Non-updatable fields:

  • Email (change requires a separate verification flow, deferred to a later phase)
  • Internal user ID

Account deletion:

  • Soft-delete: marks the account as deactivated
  • User's display name in existing content is replaced with "Deleted User"
  • Ownership of projects must be transferred before deletion (block deletion if user owns projects)
  • After 30 days, hard-delete all personal data (GDPR compliance)
  • Project memberships are removed; content authored by the user remains but is de-attributed

Auth Context Integration

Every API request must carry auth context. This is how authentication integrates with the rest of the system.

ICurrentUserService: The platform exposes an ICurrentUserService (resolved per-request) that any feature can inject to get:

  • UserId -- the internal UUIDv7
  • Email -- the verified email
  • DisplayName -- current display name
  • IsAuthenticated -- whether the request has a valid token

This service is populated by authentication middleware that validates the JWT on every request.

Middleware pipeline:

  1. JWT validation middleware extracts and validates the access token
  2. If valid, populates ICurrentUserService with claims
  3. If invalid or missing, ICurrentUserService.IsAuthenticated returns false
  4. Authorization attributes on endpoints enforce that the user is authenticated
  5. Feature-level permission checks (via IPermissionService) use the user ID from ICurrentUserService to check project roles

Data Flow

Registration Sequence

Login Sequence

Password Reset Flow

Invitation Acceptance Flow

Token Lifecycle State Diagram

Key Components

Auth Service

Wraps the chosen auth provider behind a consistent interface. Handles credential validation, account creation, password management, and token issuance. This is the only component that directly communicates with the external auth provider (or internal identity system). All other services interact through this abstraction.

Token Service

Manages the lifecycle of access tokens and refresh tokens. Responsible for JWT creation with the correct claims, refresh token generation, rotation, storage, and revocation. Implements replay detection by tracking token families.

User Service

Manages internal user records. Handles profile updates, account deactivation, and the mapping between external provider identities and internal UUIDv7 user IDs. This is the service other features call when they need user information.

Current User Service

A per-request scoped service populated by middleware. Provides the authenticated user's ID, email, and display name to any component that needs it. This is the primary integration point -- every feature depends on this service rather than reading tokens directly.

Email Service

Sends transactional emails: verification, password reset, password-changed confirmation. Delegates to an email provider (SendGrid, SES, etc.). Emails are sent asynchronously to avoid blocking API responses.

Invitation Bridge

A thin coordination layer that connects the auth system to the project membership system. When a user logs in or registers, this component checks for pending invitations associated with their email and surfaces them to the frontend. It does NOT auto-accept invitations -- acceptance is always an explicit user action.

Feature Interactions

FeatureInteraction
Project ManagementProjects reference ownerUserId. The invitation system depends on auth to validate invitee identity.
Permission & VisibilityIPermissionService depends on ICurrentUserService to get the user ID for role lookups.
Real-time & CollaborationSignalR connections authenticate via the same JWT. The hub validates the token on connection.
Content FeaturesAll content creation records createdByUserId from ICurrentUserService.
Version HistoryChange tracking records which user made each change via ICurrentUserService.

Edge Cases & Error Handling

Duplicate registration email: Return a generic error ("Unable to create account") to avoid revealing whether the email is already registered. Do not distinguish between "email taken" and other failures in the response.

Concurrent registration with same email: The auth provider and database unique constraint handle this. The second request will fail with a conflict, surfaced as a generic registration error.

Expired verification token: User requests a new verification email. The old token is invalidated.

Token refresh during deployment: If the signing key rotates during a deployment, all existing access tokens become invalid. Clients will use their refresh tokens to get new access tokens signed with the new key. Refresh tokens remain valid because they are validated by database lookup, not signature.

Auth provider outage: If the external auth provider is unavailable, login and registration fail with a 503 Service Unavailable. Existing sessions continue to work as long as their access tokens are valid (JWT validation is local). Refresh will fail until the provider recovers.

User deletes account while holding project ownership: Block the deletion. Return a 409 Conflict with a message listing the projects that must be transferred first.

Invitation token reuse after acceptance: Return 200 with the project key (idempotent). Do not create a duplicate membership.

Race condition on invitation acceptance: Use database-level constraints (unique on projectId + userId in ProjectMember) to prevent duplicate memberships if two acceptance requests arrive simultaneously.

Phase & Priority

Phase 1: Core Timeline & Projects

Authentication is a Phase 1 prerequisite -- nothing else works without it. It should be the first feature implemented.

Priority order within this feature:

  1. Registration + email verification
  2. Login + JWT issuance
  3. Token refresh + rotation
  4. ICurrentUserService middleware integration
  5. Password reset
  6. Profile management
  7. Account deletion (can be deferred to late Phase 1)