063-entra-signin #76
104
specs/063-entra-signin/plan.md
Normal file
104
specs/063-entra-signin/plan.md
Normal file
@ -0,0 +1,104 @@
|
||||
## Technical Plan: 063 — Entra Sign-in (Tenant Panel) v1
|
||||
|
||||
**Feature Branch**: `063-entra-signin`
|
||||
**Created**: 2026-01-26
|
||||
**Status**: Draft (v1)
|
||||
**Spec**: [specs/063-entra-signin/spec.md](specs/063-entra-signin/spec.md)
|
||||
|
||||
---
|
||||
|
||||
### 1. Overview & Goal Alignment
|
||||
|
||||
* **Goal**: Implement a secure, Entra ID-only sign-in flow for the Tenant Admin panel (`/admin`), safely onboarding users and routing them based on tenant memberships.
|
||||
* **Key Constraints**: No `/system` panel modification, no Graph calls during render/hydration, DB-only at render time for login/no-access pages, synchronous login flow.
|
||||
* **Clarifications Incorporated**:
|
||||
* Multi-tenant users redirect to a dedicated chooser page.
|
||||
* Disabled users are blocked from login, redirected to `/admin/login` with generic error.
|
||||
* `entra_tenant_id` and `entra_object_id` columns are `VARCHAR(36)` (or UUID for Postgres).
|
||||
|
||||
### 2. Architecture & High-Level Design
|
||||
|
||||
* **Authentication Flow**:
|
||||
1. User navigates to `/admin/login`.
|
||||
2. Presented with "Sign in with Microsoft" button (no local login fields).
|
||||
3. Clicking button initiates OIDC flow via Laravel Socialite (Entra ID provider).
|
||||
4. Redirects to `/auth/entra/redirect`.
|
||||
5. Entra ID authenticates user and redirects back to `/auth/entra/callback` with OIDC claims.
|
||||
6. Callback handler:
|
||||
* Validates claims (`tid`, `oid` present).
|
||||
* Performs upsert on `users` table based on `(entra_tenant_id, entra_object_id)`.
|
||||
* Checks user status (active/disabled). If disabled, blocks login.
|
||||
* Regenerates session.
|
||||
* Determines post-login route based on tenant memberships.
|
||||
* **User Provisioning (Upsert)**:
|
||||
* Use `User::updateOrCreate` with `['entra_tenant_id' => $tid, 'entra_object_id' => $oid]` as unique key.
|
||||
* Populate `name`, `email` from claims.
|
||||
* `entra_tenant_id`, `entra_object_id` columns as `VARCHAR(36)` (or UUID).
|
||||
* **Post-Login Routing**:
|
||||
* **0 Memberships**: Redirect to `/admin/no-access` (Filament page).
|
||||
* **1 Membership**: Redirect to that tenant’s dashboard.
|
||||
* **N Memberships**: Redirect to `/admin/choose-tenant` (dedicated Filament chooser page).
|
||||
* **Session Separation**: Leverage Laravel's guard system for clear separation between `platform` (system) and `tenant` (admin) panels.
|
||||
|
||||
### 3. Key Components & Implementation Details
|
||||
|
||||
* **Laravel Socialite**:
|
||||
* Configure Entra ID provider in `config/services.php`.
|
||||
* Create `SocialiteController` (or similar) to handle `/auth/entra/redirect` and `/auth/entra/callback`.
|
||||
* **User Model (`app/Models/User.php`)**:
|
||||
* Add `entra_tenant_id` and `entra_object_id` as fillable properties.
|
||||
* Implement logic for checking `tenants()` relationship.
|
||||
* **Migrations**:
|
||||
* Add `entra_tenant_id` (`string('entra_tenant_id', 36)->nullable()` or `uuid('entra_tenant_id')`) and `entra_object_id` (`string('entra_object_id', 36)->nullable()` or `uuid('entra_object_id')`) columns to `users` table.
|
||||
* Add unique index `unique(['entra_tenant_id', 'entra_object_id'])`.
|
||||
* **Note**: Initial migration can add as nullable, then a follow-up migration can make non-nullable if all existing users are migrated or it's a new system. Given it's a new system for `admin` sign-in, it should probably be non-nullable from the start.
|
||||
* **Filament Panel Customization**:
|
||||
* Override default Filament login page for `/admin` panel to remove email/password fields and add "Sign in with Microsoft" button.
|
||||
* Create `NoAccessPage` (`/admin/no-access`) and `TenantChooserPage` (`/admin/choose-tenant`) as Filament pages.
|
||||
* **Error Handling & Logging**:
|
||||
* Implement robust OIDC failure handling as per `FR-004`.
|
||||
* Utilize Laravel's logging facilities for privacy-safe audit logs (`FR-005`).
|
||||
* Define stable `reason_code` examples (e.g., `oidc_missing_claims`, `user_disabled`).
|
||||
* **Service Layer (Optional but Recommended)**:
|
||||
* Consider a `EntraLoginService` to encapsulate OIDC callback logic, user upsert, and routing decisions. This keeps controllers lean and business logic testable.
|
||||
|
||||
### 4. Database Schema Changes
|
||||
|
||||
* **`users` table**:
|
||||
* `entra_tenant_id` `string('entra_tenant_id', 36)->nullable()` (or `uuid('entra_tenant_id')`)
|
||||
* `entra_object_id` `string('entra_object_id', 36)->nullable()` (or `uuid('entra_object_id')`)
|
||||
* Add `unique(['entra_tenant_id', 'entra_object_id'])` index.
|
||||
* **Note**: Initial migration can add as nullable, then a follow-up migration can make non-nullable if all existing users are migrated or it's a new system. Given it's a new system for `admin` sign-in, it should probably be non-nullable from the start.
|
||||
|
||||
### 5. Test Plan (Building on Spec Acceptance Tests)
|
||||
|
||||
* **Unit Tests (Pest)**:
|
||||
* Socialite callback handler logic (claim validation, upsert logic, disabled user check).
|
||||
* User model methods related to Entra ID and tenant memberships.
|
||||
* Routing service/resolver.
|
||||
* **Feature Tests (Pest)**:
|
||||
* `AdminLoginIsEntraOnlyTest` (GET `/admin/login` renders correctly, no password inputs).
|
||||
* `EntraCallbackUpsertByTidOidTest` (callback upserts, session regenerated).
|
||||
* `PostLoginRoutingByMembershipTest` (0, 1, N memberships routing).
|
||||
* `OidcFailureRedirectsSafelyTest` (missing claims, generic error, logs).
|
||||
* `SessionSeparationSmokeTest` (guard separation works).
|
||||
* `DisabledUserLoginIsBlockedTest` (disabled user login attempt blocked and logged).
|
||||
* **Browser Tests (Pest v4)**:
|
||||
* End-to-end flow for successful Entra login.
|
||||
* Verify the `/admin/choose-tenant` page renders correctly and allows selection.
|
||||
|
||||
### 6. Deployment Considerations
|
||||
|
||||
* **Environment Variables**:
|
||||
* `ENTRA_CLIENT_ID`, `ENTRA_CLIENT_SECRET`, `ENTRA_REDIRECT_URI` for Socialite.
|
||||
* These must be managed securely (Dokploy environment variables).
|
||||
* **Migrations**: Ensure database migrations are run (`sail artisan migrate`).
|
||||
* **Filament Assets**: `php artisan filament:assets` must be run on deployment.
|
||||
* **Dokploy**: Ensure Dokploy config includes any new routes/pages.
|
||||
|
||||
### 7. Open Questions / Potential Risks
|
||||
|
||||
* **Entra ID Setup**: Assumed Entra ID application registration and configuration are handled externally.
|
||||
* **`last_tenant_id`**: Decision on whether to implement `last_tenant_id` for multi-membership users is deferred. It's an optimization.
|
||||
* **User Provisioning**: What if required claims like `email` or `name` are missing from Entra ID? Current spec implies they *should* be present, but fallback behavior is not explicitly defined for `name` (email is nullable).
|
||||
* **Authorization**: This plan focuses on authentication. Tenant-specific authorization (what actions a user can perform within a tenant) is out of scope for this feature (covered by `062-tenant-rbac-v1`).
|
||||
Loading…
Reference in New Issue
Block a user