Files
fotospiel-app/docs/prp/13-backend-authentication.md
Codex Agent 1a4bdb1fe1 tenant admin startseite schicker gestaltet und super-admin und tenant admin (filament) aufgesplittet.
Es gibt nun task collections und vordefinierte tasks für alle. Onboarding verfeinert und webseite-carousel gefixt (logging später entfernen!)
2025-10-14 15:17:52 +02:00

13 KiB

13 - Backend Authentication Implementation

Overview

This document outlines the authentication requirements and implementation details for the Fotospiel tenant backend. The system uses OAuth2 with PKCE (Proof Key for Code Exchange) for secure authorization, providing tenant-specific access tokens for API operations. Additionally, session-based authentication is used for web interfaces like the checkout wizard, supporting both email and username login.

Session-Based Authentication (Web/Checkout)

Checkout Login Flow

  • Endpoint: POST /checkout/login
  • Method: POST
  • Content-Type: application/json
  • Parameters:
    • identifier: Email or username (required, string)
    • password: User's password (required, string)
    • remember: Remember me flag (optional, boolean)
    • locale: Language locale (optional, string, e.g., 'de')

Authentication Logic:

  • Validate input using Laravel Validator.
  • Search for user by email or username using Eloquent query: User::where('email', $identifier)->orWhere('username', $identifier)->first().
  • Verify password with Hash::check().
  • If valid, log in user with Auth::login($user, $remember) and regenerate session.
  • Set pending_purchase = true if a package is selected (from session) and not already set, wrapped in DB transaction.
  • Return JSON response with user data for AJAX handling in frontend.

Response (JSON, 200 OK):

{
  "user": {
    "id": 1,
    "email": "user@example.com",
    "name": "John Doe",
    "pending_purchase": true
  },
  "message": "Login erfolgreich"
}

Error Response (JSON, 422 Unprocessable Entity):

{
  "errors": {
    "identifier": ["Ungültige Anmeldedaten."]
  }
}

Security:

  • CSRF protection via web middleware.
  • Rate limiting recommended (add throttle:6,1 middleware).
  • Password hashing with Laravel's Hash facade.
  • Session regeneration after login to prevent fixation attacks.

Integration with Standard Laravel Auth

  • Leverages AuthenticatedSessionController for core logic where possible, but custom handling for identifier flexibility and checkout context.
  • Compatible with Inertia.js for SPA responses.

OAuth2 Authentication (API)

1. Authorization Request

  • Endpoint: GET /api/v1/oauth/authorize
  • Method: GET (redirect from frontend)
  • Parameters:
    • client_id: Fixed client ID for tenant-admin-app (tenant-admin-app)
    • redirect_uri: Frontend callback URL (e.g., https://tenant-admin-app.example.com/auth/callback)
    • response_type: code
    • scope: tenant:read tenant:write (tenant-specific scopes)
    • state: CSRF protection state parameter
    • code_challenge: PKCE challenge (SHA-256 hash of code verifier)
    • code_challenge_method: S256

Response: Redirect to frontend with authorization code and state parameters.

2. Token Exchange

  • Endpoint: POST /api/v1/oauth/token
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Parameters:
    • grant_type: authorization_code or refresh_token
    • client_id: tenant-admin-app
    • code: Authorization code (for initial exchange)
    • redirect_uri: Same as authorization request
    • code_verifier: PKCE verifier (plain text)
    • refresh_token: For token refresh (optional)

Response (JSON):

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "def50200...",
  "expires_in": 3600,
  "token_type": "Bearer",
  "scope": "tenant:read tenant:write"
}

3. Token Refresh

  • Endpoint: POST /api/v1/oauth/token
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Parameters:
    • grant_type: refresh_token
    • client_id: tenant-admin-app
    • refresh_token: Current refresh token

Response: Same as token exchange (new access and refresh tokens).

4. Token Validation

  • Endpoint: GET /api/v1/tenant/me
  • Method: GET
  • Authorization: Bearer {access_token}
  • Purpose: Validate token and return tenant information

Response (JSON):

{
  "tenant_id": "tenant-uuid",
  "tenant_name": "Event Photo Company",
  "credits": 150,
  "role": "admin",
  "email": "admin@eventphoto.com"
}

Security Requirements

PKCE Implementation

  • Validate code_challenge_method is S256
  • Store code_challenge and code_verifier association temporarily (Redis, 5-minute expiry)
  • Verify SHA-256 hash of code_verifier matches stored code_challenge during token exchange
  • Reject requests without valid PKCE parameters

State Validation

  • Generate cryptographically secure state parameter
  • Store state in session or Redis with 5-minute expiry
  • Validate state parameter matches stored value during callback
  • Reject requests with state mismatch (CSRF protection)

Token Security

  • Access tokens: JWT with 1-hour expiry, signed with RS256
  • Refresh tokens: Secure random 128-character strings, stored in database
  • Refresh tokens: Single-use or rotation (new refresh token with each refresh)
  • Rate limiting on token endpoints (100 requests/hour per IP)
  • IP binding for refresh tokens (optional for enhanced security)

Scopes and Permissions

  • tenant:read: Access to tenant data (events, photos, members)
  • tenant:write: Create/update/delete tenant resources
  • tenant:admin: Full administrative access (user management, billing)
  • Token claims include tenant_id and scope for authorization

Database Schema

oauth_clients Table

CREATE TABLE oauth_clients (
  id VARCHAR(255) PRIMARY KEY,
  client_id VARCHAR(255) UNIQUE NOT NULL,
  client_secret VARCHAR(255),
  tenant_id BIGINT UNSIGNED NULL,
  redirect_uris JSON NULL,
  scopes JSON NULL,
  is_active TINYINT(1) DEFAULT 1,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  CONSTRAINT oauth_clients_tenant_id_foreign FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE SET NULL
);
CREATE TABLE oauth_clients (
  id VARCHAR(255) PRIMARY KEY,
  client_id VARCHAR(255) UNIQUE NOT NULL,
  client_secret VARCHAR(255),
  redirect_uris TEXT,
  scopes TEXT DEFAULT 'tenant:read tenant:write',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

oauth_codes Table (Temporary)

CREATE TABLE oauth_codes (
  id VARCHAR(255) PRIMARY KEY,
  client_id VARCHAR(255) NOT NULL,
  user_id VARCHAR(255) NOT NULL,
  code VARCHAR(255) UNIQUE NOT NULL,
  code_challenge VARCHAR(255) NOT NULL,
  state VARCHAR(255),
  redirect_uri VARCHAR(255),
  scope TEXT,
  expires_at TIMESTAMP NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_code (code),
  INDEX idx_expires (expires_at)
);

refresh_tokens Table

CREATE TABLE refresh_tokens (
  id VARCHAR(255) PRIMARY KEY,
  tenant_id VARCHAR(255) NOT NULL,
  client_id VARCHAR(255),
  token VARCHAR(255) UNIQUE NOT NULL,
  access_token VARCHAR(255),
  scope TEXT,
  ip_address VARCHAR(45),
  user_agent TEXT,
  expires_at TIMESTAMP,
  revoked_at TIMESTAMP NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE refresh_tokens (
  id VARCHAR(255) PRIMARY KEY,
  tenant_id VARCHAR(255) NOT NULL,
  token VARCHAR(255) UNIQUE NOT NULL,
  access_token VARCHAR(255),
  expires_at TIMESTAMP NOT NULL,
  scope TEXT,
  ip_address VARCHAR(45),
  user_agent TEXT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  revoked_at TIMESTAMP NULL,
  INDEX idx_tenant (tenant_id),
  INDEX idx_token (token),
  INDEX idx_expires (expires_at)
);

tenant_tokens Table (JWT Blacklist)

CREATE TABLE tenant_tokens (
  id VARCHAR(255) PRIMARY KEY,
  tenant_id VARCHAR(255) NOT NULL,
  jti VARCHAR(255) UNIQUE NOT NULL, -- JWT ID
  token_type VARCHAR(50) NOT NULL,
  expires_at TIMESTAMP NOT NULL,
  revoked_at TIMESTAMP NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_jti (jti),
  INDEX idx_tenant (tenant_id),
  INDEX idx_expires (expires_at)
);

API Endpoints

Authentication Endpoints

Endpoint Method Description Authentication
/oauth/authorize GET Authorization request None
/oauth/token POST Token exchange/refresh None
/api/v1/tenant/me GET Validate token Bearer Token
/checkout/login POST Session login for checkout (email/username) None

Protected Endpoints

All tenant API endpoints require Authorization: Bearer {access_token} header.

Token Validation Middleware

// Pseudocode
function validateTenantToken(req, res, next) {
  const token = req.headers.authorization?.replace('Bearer ', '');
  if (!token) return res.status(401).json({ error: 'Missing token' });

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    const { tenant_id, scope, exp, jti } = decoded;

    // Check if token is blacklisted
    const blacklisted = await db.query('SELECT * FROM tenant_tokens WHERE jti = ? AND revoked_at IS NULL', [jti]);
    if (blacklisted.length > 0) {
      return res.status(401).json({ error: 'Token revoked' });
    }

    // Check expiry
    if (Date.now() >= exp * 1000) {
      return res.status(401).json({ error: 'Token expired' });
    }

    // Set tenant context
    req.tenant = { id: tenant_id, scope };
    next();
  } catch (error) {
    res.status(401).json({ error: 'Invalid token' });
  }
}

Environment Variables

Backend (.env)

JWT_SECRET=your-super-secret-jwt-signing-key
API_BASE_URL=https://api.fotospiel.com
OAUTH_CLIENT_SECRET=your-oauth-client-secret
DATABASE_URL=your-database-connection-string
REDIS_URL=redis://localhost:6379

Frontend (.env)

VITE_API_URL=https://api.fotospiel.com
VITE_OAUTH_CLIENT_ID=tenant-admin-app

Hinweis: Der Wert von VITE_OAUTH_CLIENT_ID dient jetzt als alleinige Quelle der Wahrheit für den Tenant-Admin-OAuth-Client. Der Seeder OAuthClientSeeder greift auf config/services.php zu, das wiederum diesen Env-Wert ausliest und passende Redirect-URIs generiert (/event-admin/auth/callback für DEV und APP_URL). Stimmt der Wert im Frontend nicht mit dem Seeder überein, schlägt der PKCE-Login mit invalid_client fehl.

Error Handling

Common Error Responses

// 400 Bad Request
{
  "error": "invalid_request",
  "error_description": "Missing required parameter: code"
}

// 401 Unauthorized
{
  "error": "invalid_token",
  "error_description": "Token signature invalid"
}

// 403 Forbidden
{
  "error": "insufficient_scope",
  "error_description": "Scope tenant:write required"
}

// 422 Unprocessable Entity (Checkout Login)
{
  "errors": {
    "identifier": ["Ungültige Anmeldedaten."]
  }
}

Implementation Notes

1. PKCE Storage

  • Use Redis for temporary code_challenge storage (5-minute TTL)
  • Key format: pkce:{code_challenge}:{client_id}
  • Value: JSON with code_verifier, user_id, redirect_uri, scope

2. Refresh Token Rotation

  • Issue new refresh token with each refresh
  • Revoke old refresh token immediately
  • Limit refresh tokens per tenant to 5 active

3. Key Management

  • RSA key pairs for signing are generated on demand and stored in storage/app/private.key (private) and storage/app/public.key (public).
  • Treat the private key as a secret; rotate it alongside deploys that invalidate tenant tokens.

4. Rate Limiting

  • Authorization requests: 10/minute per IP
  • Token exchanges: 5/minute per IP
  • Token validation: 100/minute per tenant
  • Checkout login: 6/minute per IP (add throttle middleware)

5. Logging and Monitoring

  • Log all authentication attempts (success/failure)
  • Monitor token usage patterns
  • Alert on unusual activity (multiple failed attempts, token anomalies)
  • Track refresh token usage for security analysis
  • Log checkout login attempts with identifier type (email/username)

6. Database Cleanup

  • Cron job to remove expired authorization codes (daily)
  • Remove expired refresh tokens (weekly)
  • Clean blacklisted tokens after expiry (daily)

Testing Requirements

Unit Tests

  • PKCE generation and validation
  • State parameter security
  • Token signing and verification
  • Scope validation middleware
  • Checkout login with email and username

Integration Tests

  • Complete OAuth2 flow (authorize → token → validate)
  • Token refresh cycle
  • Error scenarios (invalid code, expired tokens, state mismatch)
  • Concurrent access testing
  • Checkout login flow with pending_purchase

Security Tests

  • CSRF protection validation
  • PKCE bypass attempts
  • Token replay attacks
  • Rate limiting enforcement
  • Username/email ambiguity handling

Deployment Considerations

1. Secrets Management

  • Store JWT secret and OAuth client secret in secure vault (AWS Secrets Manager, HashiCorp Vault)
  • Rotate secrets every 90 days
  • Use different secrets for dev/staging/production

2. Certificate Management

  • Use Let's Encrypt or commercial SSL certificates
  • Rotate certificates before expiry
  • Enable HSTS headers

3. Monitoring

  • Track authentication success/failure rates
  • Monitor token expiry patterns
  • Alert on PKCE validation failures
  • Log all security-related events
  • Monitor checkout login success rates and identifier usage

This implementation provides secure, scalable authentication for the Fotospiel tenant system, following OAuth2 best practices with PKCE for public clients and flexible session auth for web flows.