TenantAtlas/specs/063-entra-signin/plan.md
ahmido c5fbcaa692 063-entra-signin (#76)
Key changes

Adds Entra OIDC redirect + callback endpoints under /auth/entra/* (token exchange only there).
Upserts tenant users keyed by (entra_tenant_id = tid, entra_object_id = oid); regenerates session; never stores tokens.
Blocks disabled / soft-deleted users with a generic error and safe logging.
Membership-based post-login routing:
0 memberships → /admin/no-access
1 membership → tenant dashboard (via Filament URL helpers)
>1 memberships → /admin/choose-tenant
Adds Filament pages:
/admin/choose-tenant (tenant selection + redirect)
/admin/no-access (tenantless-safe)
Both use simple layout to avoid tenant-required UI.
Guards / tests

Adds DbOnlyPagesDoNotMakeHttpRequestsTest to enforce DB-only render/hydration for:
/admin/login, /admin/no-access, /admin/choose-tenant
with Http::preventStrayRequests()
Adds session separation smoke coverage to ensure tenant session doesn’t access system and vice versa.
Runs: vendor/bin/sail artisan test --compact tests/Feature/Auth

Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box>
Reviewed-on: #76
2026-01-27 16:38:53 +00:00

4.9 KiB
Raw Blame History

Implementation Plan: 063 — Entra Sign-in (Tenant Panel) v1

Branch: 063-entra-signin | Date: 2026-01-27 | Spec: specs/063-entra-signin/spec.md Input: Feature specification from /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/063-entra-signin/spec.md

Summary

This feature will implement Microsoft Entra ID OIDC-based sign-in for the Tenant Admin panel. The implementation will override the default Filament login page to present an Entra-only sign-in option. It will utilize Laravel Socialite to manage the OAuth2 flow, handling redirection to Microsoft Entra ID and processing the callback. Core logic involves upserting users based on (entra_tenant_id, entra_object_id), blocking disabled users, regenerating sessions, and dynamically routing users post-login to their tenant dashboard, a dedicated tenant chooser page (for multiple memberships), or a "no access" page (for zero memberships). The implementation adheres to security best practices by not storing sensitive tokens and ensures all affected pages remain DB-only at render time.

Routing stability rule: redirects into a tenant MUST use Filament page URL helpers (e.g., App\\Filament\\Pages\\TenantDashboard::getUrl(tenant: $tenant)) rather than hardcoding /admin/t/..., so future route prefix / tenant slug changes dont break auth flows.

Technical Context

Language/Version: PHP 8.4 Primary Dependencies: laravel/framework:^12, livewire/livewire:^4, filament/filament:^5, laravel/socialite:^5.0 Storage: PostgreSQL Testing: Pest Target Platform: Web Project Type: Web application Performance Goals: Callback returns within ~2s under normal conditions. Constraints: Do not persist secrets/tokens. Sanitize all error output and logs. Outbound HTTP is permitted only inside /auth/entra/* endpoints. Scale/Scope: Tenant Admin panel (/admin) sign-in only.

Constitution Check

GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.

  • Inventory-first: Not directly applicable to auth.
  • Read/write separation: N/A for login, but user upsert is a write. The spec requires it to be idempotent, which aligns.
  • Graph contract path: The spec explicitly forbids Graph calls during render/poll/hydration, and limits OIDC calls to the /auth/entra/* routes. This is compliant.
  • Deterministic capabilities: N/A.
  • Tenant isolation: Compliant. The entire flow is built around tenant context (tid).
  • Run observability: Compliant. The spec references OPS-EX-AUTH-001, the Auth Handshake Exception, which exempts this synchronous login flow from requiring an OperationRun. Logging requirements are specified.
  • Automation: N/A.
  • Data minimization: Compliant. Spec says "MUST NOT store Entra access/refresh tokens" and requires safe logging.
  • Badge semantics (BADGE-001): N/A.

Result: The plan is compliant with the constitution.

Project Structure

Documentation (this feature)

specs/063-entra-signin/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│   └── entra-auth-flow.md
└── tasks.md

Source Code (repository root)

# Web application
app/
├── Http/
│   ├── Controllers/
│   │   └── Auth/
│   │       └── EntraController.php   # New: Handles OIDC redirect and callback logic
│   └── Middleware/
├── Filament/
│   ├── Pages/
│   │   ├── Auth/
│   │   │   └── Login.php           # New: Custom Filament login page with Entra-only CTA
│   │   ├── NoAccess.php            # New: Page for users with no tenant memberships
│   │   └── ChooseTenant.php        # New: Page for users with multiple tenant memberships
│   └── Tenant/
│       └── Resources/
├── Models/
│   └── User.php                    # Modified: User model for Entra IDs and tenant relationships
├── Providers/
│   └── Filament/
│       └── AdminPanelProvider.php  # Modified: Register custom login page
config/
├── services.php                    # Modified: Add Microsoft Socialite provider config
routes/
│   └── web.php                     # Modified: Register OIDC redirect and callback routes
tests/
└── Feature/
    └── Auth/
        ├── AdminLoginIsEntraOnlyTest.php
        ├── EntraCallbackUpsertByTidOidTest.php
        ├── PostLoginRoutingByMembershipTest.php
        ├── OidcFailureRedirectsSafelyTest.php
        ├── SessionSeparationSmokeTest.php
        └── DisabledUserLoginIsBlockedTest.php

Structure Decision: The project is a standard Laravel web application. The changes will be implemented within the existing structure, primarily affecting app/, routes/, config/ and tests/.

Complexity Tracking

No violations.