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
96 lines
4.9 KiB
Markdown
96 lines
4.9 KiB
Markdown
# Implementation Plan: 063 — Entra Sign-in (Tenant Panel) v1
|
||
|
||
**Branch**: `063-entra-signin` | **Date**: 2026-01-27 | **Spec**: [specs/063-entra-signin/spec.md](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 don’t 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)
|
||
|
||
```text
|
||
specs/063-entra-signin/
|
||
├── plan.md
|
||
├── research.md
|
||
├── data-model.md
|
||
├── quickstart.md
|
||
├── contracts/
|
||
│ └── entra-auth-flow.md
|
||
└── tasks.md
|
||
```
|
||
|
||
### Source Code (repository root)
|
||
|
||
```text
|
||
# 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. |