425 lines
17 KiB
Markdown
425 lines
17 KiB
Markdown
# Enterprise SSO PRD (SAML 2.0 + OIDC)
|
|||
|
|
|
||
|
|
## Purpose
|
||
|
|
Add enterprise SSO for ClassroomIO with per-organization configuration, supporting both SAML 2.0 and OIDC, while preserving the current Better Auth session model and multi-tenant org permissions.
|
||
|
|
|
||
|
|
## Confirmed Decisions (2026-02-17)
|
||
|
|
1. IdPs to support: Auth0, Google Workspace, Okta.
|
||
|
|
2. Scope: per organization (not global tenant-wide config).
|
||
|
|
3. Protocols: OIDC for v1. SAML 2.0 deferred to v2.
|
||
|
|
4. Force SSO: enabled (orgs can disable email/password when SSO is enabled).
|
||
|
|
5. Provisioning: JIT only for v1. SCIM deferred to future phase.
|
||
|
|
6. SAML deferred to v2 (Better Auth SAML is in active development).
|
||
|
|
7. Break-glass: simplified emergency access via pre-configured admin bypass.
|
||
|
|
8. Domain enforcement: simplified to auto-join toggle.
|
||
|
|
9. Entitlement: SSO is gated to `PLAN.ENTERPRISE`.
|
||
|
|
10. SCIM deprovisioning: future phase, with interim login-time entitlement checks.
|
||
|
|
|
||
|
|
## Architecture: Better Auth SSO Plugin
|
||
|
|
|
||
|
|
v1 leverages **Better Auth's official SSO plugin** (`@better-auth/sso`) for OIDC support:
|
||
|
|
- Built-in OIDC discovery and flow handling
|
||
|
|
- Automatic callback URL generation
|
||
|
|
- Domain-based provider routing
|
||
|
|
- Claim mapping support
|
||
|
|
- Session management via Better Auth
|
||
|
|
|
||
|
|
Our custom layer handles:
|
||
|
|
- Enterprise plan entitlement checks
|
||
|
|
- Organization-scoped provider management
|
||
|
|
- Force SSO policy enforcement
|
||
|
|
- Org membership JIT provisioning
|
||
|
|
- Admin configuration UI
|
||
|
|
|
||
|
|
## Current-State Audit
|
||
|
|
|
||
|
|
| Capability | Current State | Notes |
|
||
|
|
| --- | --- | --- |
|
||
|
|
| Auth framework | available | Better Auth with cookie sessions (`packages/db/src/auth.ts`) |
|
||
|
|
| Email/password auth | available | Enabled via `emailAndPassword` (`packages/db/src/auth/email-password.ts`) |
|
||
|
|
| Social login | partial | Google social login only (`packages/db/src/auth.ts`, `apps/dashboard/src/lib/features/ui/auth-ui.svelte`) |
|
||
|
|
| Session handling | available | `/api/auth/*` mounted in API, `auth.api.getSession` middleware (`apps/api/src/app.ts`) |
|
||
|
|
| Multi-tenant org model | available | `organization`, `organizationmember`, org role checks and `cio-org-id` header enforcement |
|
||
|
|
| Org invite flows | available | Role-aware organization invites already implemented |
|
||
|
|
| Enterprise SSO (SAML/OIDC per org) | missing | No SSO connection model, no discovery, no SAML/OIDC enterprise callback flows |
|
||
|
|
| Force SSO policy | missing | No org-level policy to block local email/password |
|
||
|
|
| SCIM | missing | No SCIM endpoints or provisioning sync |
|
||
|
|
|
||
|
|
## Product Goals
|
||
|
|
1. Allow each organization to configure one SSO connection.
|
||
|
|
2. Support Auth0, Google Workspace, and Okta with OIDC.
|
||
|
|
3. Preserve existing Better Auth sessions/cookies and downstream auth middleware behavior.
|
||
|
|
4. Add Force SSO policy at org level.
|
||
|
|
5. Keep invite/onboarding flows compatible with SSO.
|
||
|
|
6. Ship JIT provisioning for v1.
|
||
|
|
7. Add simple domain-based auto-join policy.
|
||
|
|
8. Gate all SSO capabilities to Enterprise plan organizations.
|
||
|
|
|
||
|
|
## Non-Goals (v1)
|
||
|
|
- **SAML 2.0** (deferred to v2 - Better Auth SAML is in active development).
|
||
|
|
- SCIM implementation (deferred to future phase).
|
||
|
|
- Multiple SSO connections per organization.
|
||
|
|
- Custom claim mapping UI (use standard claims).
|
||
|
|
- Global logout / SAML Single Logout (SLO).
|
||
|
|
- Arbitrary custom IdP templates beyond Auth0/Google Workspace/Okta.
|
||
|
|
- Cross-organization identity linking rules beyond current email + org membership model.
|
||
|
|
|
||
|
|
## Data Sources Checked
|
||
|
|
- `packages/db/src/auth.ts`
|
||
|
|
- `packages/db/src/auth/email-password.ts`
|
||
|
|
- `apps/api/src/app.ts`
|
||
|
|
- `apps/api/src/middlewares/auth.ts`
|
||
|
|
- `apps/api/src/middlewares/org-member.ts`
|
||
|
|
- `apps/api/src/middlewares/org-admin.ts`
|
||
|
|
- `packages/db/src/schema.ts`
|
||
|
|
- `packages/db/src/queries/organization/organization.ts`
|
||
|
|
- `apps/api/src/services/account.ts`
|
||
|
|
- `apps/api/src/services/organization/invite.ts`
|
||
|
|
- `apps/api/src/services/course/invite.ts`
|
||
|
|
- `apps/dashboard/src/lib/utils/services/auth/client.ts`
|
||
|
|
- `apps/dashboard/src/lib/utils/services/auth/session.ts`
|
||
|
|
- `apps/dashboard/src/routes/(auth)/login/+page.svelte`
|
||
|
|
- `apps/dashboard/src/lib/features/ui/auth-ui.svelte`
|
||
|
|
- `apps/dashboard/src/lib/features/app/init.svelte.ts`
|
||
|
|
- `apps/dashboard/src/lib/features/ui/navigation/org-navigation.ts`
|
||
|
|
- `apps/docs/content/docs/quickstart/self-hosting.mdx`
|
||
|
|
- `apps/docs/content/docs/meta.json`
|
||
|
|
- `apps/docs/content/docs/quickstart/meta.json`
|
||
|
|
|
||
|
|
## Functional Requirements
|
||
|
|
|
||
|
|
### 1. Per-Organization SSO Connection Management
|
||
|
|
- Org admins can create/update/disable one SSO connection per org.
|
||
|
|
- Supported providers: Auth0, Google Workspace, Okta.
|
||
|
|
- Supported protocols: OIDC and SAML 2.0.
|
||
|
|
- Connection supports test/verify before activation.
|
||
|
|
|
||
|
|
### 2. SSO Discovery and Login Start
|
||
|
|
- On org domains (`{org}.domain.com` or custom domain), login should use that org context directly.
|
||
|
|
- On shared app domain, user can enter email to discover SSO route by org policy/domain mapping.
|
||
|
|
- Discovery response should indicate:
|
||
|
|
- SSO required or optional
|
||
|
|
- Provider type
|
||
|
|
- Whether password login is allowed
|
||
|
|
|
||
|
|
### 3. OIDC Flow (Better Auth SSO Plugin)
|
||
|
|
Better Auth handles the OIDC flow:
|
||
|
|
- Authorization code flow with PKCE, `state`, and `nonce`.
|
||
|
|
- Auto-discovery from `.well-known/openid-configuration`.
|
||
|
|
- Token validation and userinfo retrieval.
|
||
|
|
- Claim mapping to user fields.
|
||
|
|
|
||
|
|
Our layer handles:
|
||
|
|
- Provider registration via `auth.api.registerSSOProvider`.
|
||
|
|
- Post-sign-in org membership provisioning via `dbHooks`.
|
||
|
|
- Role mapping from `groups` claim.
|
||
|
|
|
||
|
|
### 4. SAML 2.0 Flow (v2)
|
||
|
|
- **Deferred to v2** - will be implemented once Better Auth's SAML support is stable.
|
||
|
|
|
||
|
|
### 5. JIT Provisioning and Membership Linking
|
||
|
|
- On successful SSO callback:
|
||
|
|
- Find or create user/profile
|
||
|
|
- Upsert org membership in `organizationmember`
|
||
|
|
- Link external identity (`connection + subject`) to local user
|
||
|
|
- If invited member row exists by email, bind `profileId` and mark verified
|
||
|
|
- Default JIT role for new users: `ROLE.STUDENT`
|
||
|
|
|
||
|
|
### 6. Role Mapping
|
||
|
|
- Simple role mapping from IdP `groups` claim:
|
||
|
|
- Map group names to `ROLE.ADMIN`, `ROLE.TUTOR`, `ROLE.STUDENT`
|
||
|
|
- If no mapping matched, fallback to `ROLE.STUDENT`
|
||
|
|
- Configuration: simple key-value mapping in connection settings
|
||
|
|
|
||
|
|
### 7. Force SSO
|
||
|
|
- Org-level `forceSso=true` disables email/password login for that org context
|
||
|
|
- Enforce server-side, not only UI-level
|
||
|
|
- Users attempting password login for a force-SSO org receive structured error + SSO redirect hints
|
||
|
|
- Existing email/password sessions: remain valid until expiry (no forced logout)
|
||
|
|
- Break-glass: pre-configured emergency admin can bypass (audited)
|
||
|
|
|
||
|
|
### 8. Audit and Observability
|
||
|
|
- Use existing audit/logging infrastructure with structured event types:
|
||
|
|
- `sso.connection_created`, `sso.connection_updated`, `sso.connection_disabled`
|
||
|
|
- `sso.login_success`, `sso.login_failure`
|
||
|
|
- `sso.provisioned`, `sso.role_assigned`
|
||
|
|
- `sso.force_sso_block`
|
||
|
|
- Include orgId, connectionId, actor/profile (when available), IP, user agent
|
||
|
|
|
||
|
|
### 9. Domain Auto-Join Policy
|
||
|
|
- Simple toggle: `auto_join_sso_domains` (boolean)
|
||
|
|
- When enabled, users with matching email domain can auto-join org via SSO
|
||
|
|
- When disabled, users must have pending invite to join via SSO
|
||
|
|
- Trust IdP-verified email domains
|
||
|
|
|
||
|
|
### 10. Enterprise Plan Entitlement
|
||
|
|
- SSO configuration and login endpoints must verify org entitlement to `PLAN.ENTERPRISE`
|
||
|
|
- Non-enterprise orgs receive explicit upgrade-required responses
|
||
|
|
|
||
|
|
## Technical Design
|
||
|
|
|
||
|
|
### Architecture Summary
|
||
|
|
Leverage **Better Auth SSO plugin** (`@better-auth/sso`) for OIDC protocol handling. Our custom layer provides:
|
||
|
|
1. **Admin management** - CRUD SSO connections (stored in our schema for UI)
|
||
|
|
2. **Enterprise gating** - Verify `PLAN.ENTERPRISE` before allowing SSO
|
||
|
|
3. **Force SSO enforcement** - Block email/password when policy enabled
|
||
|
|
4. **JIT provisioning** - Create org membership post-authentication via `dbHooks`
|
||
|
|
|
||
|
|
Better Auth handles:
|
||
|
|
- OIDC discovery, authorization, token exchange
|
||
|
|
- Session creation and management
|
||
|
|
- User creation/linking
|
||
|
|
|
||
|
|
### Data Model
|
||
|
|
|
||
|
|
#### `organization_auth_policy`
|
||
|
|
- `organization_id` (uuid, pk/fk)
|
||
|
|
- `force_sso` (boolean, default `false`)
|
||
|
|
- `default_role_id` (bigint, default `ROLE.STUDENT`)
|
||
|
|
- `allow_email_password_fallback` (boolean, default `true`)
|
||
|
|
- `auto_join_sso_domains` (boolean, default `false`)
|
||
|
|
- `break_glass_enabled` (boolean, default `true`)
|
||
|
|
- `created_at`, `updated_at`
|
||
|
|
|
||
|
|
#### `organization_sso_connection`
|
||
|
|
- `id` (uuid, pk)
|
||
|
|
- `organization_id` (uuid, fk, indexed, unique)
|
||
|
|
- `provider` (`OKTA` | `GOOGLE_WORKSPACE` | `AUTH0`)
|
||
|
|
- `protocol` (`OIDC`) - enum for future SAML extension
|
||
|
|
- `display_name` (text)
|
||
|
|
- `is_active` (boolean, default `false`)
|
||
|
|
- `issuer` (text) - IdP issuer URL
|
||
|
|
- `domain` (text) - email domain for discovery
|
||
|
|
- `oidc_config` (jsonb) - clientId, scopes, etc (clientSecret stored in Better Auth)
|
||
|
|
- `role_mapping` (jsonb) - simple key-value: group name → role
|
||
|
|
- `better_auth_provider_id` (text) - reference to Better Auth's stored provider
|
||
|
|
- `created_by_profile_id`, `updated_by_profile_id` (uuid)
|
||
|
|
- `created_at`, `updated_at`
|
||
|
|
|
||
|
|
#### `sso_identity_link`
|
||
|
|
- `id` (uuid, pk)
|
||
|
|
- `organization_id` (uuid, indexed)
|
||
|
|
- `connection_id` (uuid, fk)
|
||
|
|
- `user_id` (uuid, fk to `user.id`)
|
||
|
|
- `external_subject` (text)
|
||
|
|
- `email_at_link` (text)
|
||
|
|
- `last_login_at` (timestamp)
|
||
|
|
- `created_at`, `updated_at`
|
||
|
|
|
||
|
|
Constraints:
|
||
|
|
- unique `(connection_id, external_subject)`
|
||
|
|
- unique `(organization_id, user_id, connection_id)`
|
||
|
|
|
||
|
|
## API and Route Plan
|
||
|
|
|
||
|
|
### Better Auth Integration
|
||
|
|
The SSO plugin provides these endpoints automatically:
|
||
|
|
- `POST /api/auth/sso/register` - Register OIDC provider (admin only)
|
||
|
|
- `GET/POST /api/auth/sso/authorize/:providerId` - Start SSO flow
|
||
|
|
- `GET/POST /api/auth/sso/callback/:providerId` - OIDC callback
|
||
|
|
|
||
|
|
### Our Custom Layer
|
||
|
|
|
||
|
|
#### Validation Layer
|
||
|
|
- `packages/utils/src/validation/organization/sso.ts`
|
||
|
|
- `ZCreateSsoConnection`
|
||
|
|
- `ZUpdateSsoConnection`
|
||
|
|
- `ZSsoDiscoveryQuery`
|
||
|
|
|
||
|
|
#### Query Layer
|
||
|
|
- `packages/db/src/queries/organization/sso.ts`
|
||
|
|
- connection CRUD
|
||
|
|
- org auth policy read/write
|
||
|
|
|
||
|
|
#### Service Layer
|
||
|
|
- `apps/api/src/services/organization/sso.ts`
|
||
|
|
- Admin CRUD with Enterprise plan check
|
||
|
|
- Call `auth.api.registerSSOProvider` on activation
|
||
|
|
- Call `auth.api.deleteSSOProvider` on deactivation
|
||
|
|
- `apps/api/src/services/auth/sso.ts`
|
||
|
|
- Discovery logic
|
||
|
|
- Force SSO enforcement
|
||
|
|
|
||
|
|
#### Route Layer
|
||
|
|
- `apps/api/src/routes/organization/sso.ts` - Admin CRUD endpoints
|
||
|
|
- `apps/api/src/routes/sso/discovery.ts` - Public discovery endpoint
|
||
|
|
- Register in:
|
||
|
|
- `apps/api/src/routes/organization/index.ts`
|
||
|
|
- `apps/api/src/app.ts`
|
||
|
|
|
||
|
|
#### Better Auth Hooks
|
||
|
|
- `dbHooks.user.afterCreate` - JIT org membership provisioning
|
||
|
|
|
||
|
|
## Frontend Plan (Dashboard)
|
||
|
|
|
||
|
|
### Login UX
|
||
|
|
- Update:
|
||
|
|
- `apps/dashboard/src/routes/(auth)/login/+page.svelte`
|
||
|
|
- `apps/dashboard/src/lib/features/ui/auth-ui.svelte`
|
||
|
|
- Add SSO discovery step:
|
||
|
|
- org-site context: direct SSO action for current org
|
||
|
|
- shared app login: email-based discovery before method selection
|
||
|
|
- If Force SSO for org, hide password fields and show SSO-only CTA
|
||
|
|
|
||
|
|
### Org Settings UX
|
||
|
|
- Add new page: `apps/dashboard/src/routes/org/[slug]/settings/sso/+page.svelte`
|
||
|
|
- Add settings feature page: `apps/dashboard/src/lib/features/settings/pages/sso.svelte`
|
||
|
|
- Export page in `apps/dashboard/src/lib/features/settings/pages/index.ts`
|
||
|
|
- Add org navigation nested route in `apps/dashboard/src/lib/features/ui/navigation/org-navigation.ts`
|
||
|
|
- Add controls for:
|
||
|
|
- Provider selection (Auth0, Google Workspace, Okta)
|
||
|
|
- Protocol selection (OIDC, SAML)
|
||
|
|
- Force SSO toggle
|
||
|
|
- Auto-join toggle
|
||
|
|
- Simple role mapping
|
||
|
|
- Break-glass enable/disable
|
||
|
|
- Add translation keys in `apps/dashboard/src/lib/utils/translations/en.json`
|
||
|
|
|
||
|
|
### Admin SSO Configuration UI Spec
|
||
|
|
|
||
|
|
#### Page Information Architecture
|
||
|
|
- `Connection` section:
|
||
|
|
- Current SSO connection status (provider, protocol, active/inactive)
|
||
|
|
- Actions: `Configure`, `Edit`, `Disable`, `Test`, `Activate`
|
||
|
|
- `Policies` section:
|
||
|
|
- Force SSO toggle
|
||
|
|
- Auto-join toggle
|
||
|
|
- Break-glass toggle
|
||
|
|
- `Role Mapping` section:
|
||
|
|
- Simple table: IdP group name → ClassroomIO role
|
||
|
|
|
||
|
|
#### Setup Wizard (Create/Edit Connection)
|
||
|
|
- Step 1 `Choose Provider`:
|
||
|
|
- Provider: `Auth0`, `Google Workspace`, `Okta`
|
||
|
|
- Protocol: `OIDC` or `SAML`
|
||
|
|
- Step 2 `Configure Identity Provider`:
|
||
|
|
- Render provider/protocol-specific fields
|
||
|
|
- Show generated callback values to copy into IdP
|
||
|
|
- Step 3 `Test Connection` (optional):
|
||
|
|
- Run live test with state/nonce checks
|
||
|
|
- Display pass/fail results with remediation hints
|
||
|
|
- Step 4 `Activate`:
|
||
|
|
- Summary of config
|
||
|
|
- Activate now or save as draft
|
||
|
|
- If Force SSO enabled, require explicit confirmation
|
||
|
|
|
||
|
|
#### Provider-Specific Form Fields (OIDC Only)
|
||
|
|
- Common fields:
|
||
|
|
- `display_name` (required)
|
||
|
|
- `domain` (required) - email domain for discovery (e.g., `company.com`)
|
||
|
|
- OIDC fields:
|
||
|
|
- `issuer` (required URL) - e.g., `https://company.okta.com`
|
||
|
|
- `client_id` (required)
|
||
|
|
- `client_secret` (required) - sent to Better Auth, not stored locally
|
||
|
|
- `scopes` (default `openid profile email`)
|
||
|
|
- Read-only: callback URL (`/api/auth/sso/callback/{providerId}`)
|
||
|
|
|
||
|
|
#### Validation Rules and Error UX
|
||
|
|
- Field-level validation:
|
||
|
|
- URLs must be valid HTTPS (localhost exception for dev)
|
||
|
|
- Required fields present before activation
|
||
|
|
- Claim mapping must include `email` and `sub`/`NameID`
|
||
|
|
- Cross-field validation:
|
||
|
|
- `force_sso=true` requires active connection
|
||
|
|
- Connection can be saved as draft without full validation
|
||
|
|
- Error presentation:
|
||
|
|
- Inline field errors for input validation
|
||
|
|
- Structured backend errors for: issuer mismatch, invalid cert, callback mismatch, missing claims
|
||
|
|
|
||
|
|
#### Safety and Recovery UX
|
||
|
|
- Draft mode supported; drafts cannot be used for login
|
||
|
|
- Disable/rollback action always available to org admins
|
||
|
|
- All admin actions write visible audit entries
|
||
|
|
|
||
|
|
### Client Types/API Class
|
||
|
|
- Add request types in `apps/dashboard/src/lib/features/org/utils/types.ts`
|
||
|
|
- Add API class in `apps/dashboard/src/lib/features/org/api/sso.svelte.ts`
|
||
|
|
- Use `this.execute<RequestType>()` pattern only
|
||
|
|
|
||
|
|
## Documentation Plan (`apps/docs`)
|
||
|
|
|
||
|
|
### Required Documentation Updates
|
||
|
|
- Add core feature documentation:
|
||
|
|
- `apps/docs/content/docs/enterprise-sso.mdx` - feature overview
|
||
|
|
- Add how-to guides:
|
||
|
|
- `apps/docs/content/docs/how-to/sso-auth0.mdx`
|
||
|
|
- `apps/docs/content/docs/how-to/sso-google-workspace.mdx`
|
||
|
|
- `apps/docs/content/docs/how-to/sso-okta.mdx`
|
||
|
|
- Register pages in root docs nav:
|
||
|
|
- `apps/docs/content/docs/meta.json` under `---Core Features---`
|
||
|
|
- Update self-hosting reference:
|
||
|
|
- `apps/docs/content/docs/quickstart/self-hosting.mdx`
|
||
|
|
- Include SSO-related env vars and callback URL guidance
|
||
|
|
|
||
|
|
### Required Topics in Docs
|
||
|
|
- SSO prerequisites and Enterprise plan requirement
|
||
|
|
- Provider-by-provider setup checklist (Auth0, Google Workspace, Okta)
|
||
|
|
- Force SSO behavior and user experience
|
||
|
|
- JIT behavior and invite interactions
|
||
|
|
- Break-glass policy
|
||
|
|
- Troubleshooting: callback mismatch, certificate issues, entitlement errors
|
||
|
|
|
||
|
|
## Security Requirements
|
||
|
|
1. Encrypt all SSO secrets at rest
|
||
|
|
2. Strict signature/issuer/audience checks for SAML/OIDC
|
||
|
|
3. Anti-CSRF and replay protection via `state`/`nonce` and one-time callback state
|
||
|
|
4. Rate-limit discovery/start/callback endpoints
|
||
|
|
5. Full audit trail for admin config and auth outcomes via existing logging
|
||
|
|
6. No trust of frontend-enforced Force SSO alone; must be validated on backend
|
||
|
|
|
||
|
|
## Rollout Plan
|
||
|
|
|
||
|
|
### Phase 1: Foundation + OIDC + JIT
|
||
|
|
- Install `@better-auth/sso` plugin
|
||
|
|
- Schema + policy/connection management
|
||
|
|
- Integration with Better Auth SSO plugin
|
||
|
|
- OIDC flows for Auth0/Google Workspace/Okta via Better Auth
|
||
|
|
- SSO discovery + login UI
|
||
|
|
- Force SSO enforcement
|
||
|
|
- Domain auto-join policy + admin settings UI
|
||
|
|
- Enterprise plan entitlement checks
|
||
|
|
- JIT org membership via `dbHooks`
|
||
|
|
- Publish docs updates
|
||
|
|
|
||
|
|
### Phase 2: SAML 2.0
|
||
|
|
- Enable when Better Auth SAML is stable
|
||
|
|
- SAML flows for supported providers
|
||
|
|
- Force SSO parity for SAML
|
||
|
|
|
||
|
|
### Phase 3: Future Enhancements
|
||
|
|
- SCIM provisioning (if customer demand)
|
||
|
|
- Multiple connections per org (if customer demand)
|
||
|
|
- Advanced claim mapping (if customer demand)
|
||
|
|
|
||
|
|
## Acceptance Criteria
|
||
|
|
1. Org admin can configure an active SSO connection (OIDC).
|
||
|
|
2. User can successfully login through Auth0, Google Workspace, and Okta.
|
||
|
|
3. Successful SSO login creates/links user and org membership via JIT.
|
||
|
|
4. Force SSO org cannot authenticate through email/password for org-context access.
|
||
|
|
5. Auto-join policy is configurable in UI and enforced server-side.
|
||
|
|
6. SSO access is blocked for non-enterprise orgs.
|
||
|
|
7. Existing non-SSO orgs keep current auth behavior unchanged.
|
||
|
|
8. Audit events are logged for config changes and SSO auth events.
|
||
|
|
9. `apps/docs` includes SSO feature docs and provider how-to guides before GA.
|
||
|
|
10. Org admins can configure Auth0, Google Workspace, and Okta through a setup wizard.
|
||
|
|
11. Connection can be saved as draft without full validation.
|
||
|
|
12. UI surfaces actionable errors for provider setup and configuration issues.
|
||
|
|
|
||
|
|
## Risks and Mitigations
|
||
|
|
- Risk: Force SSO lockout due to bad config.
|
||
|
|
- Mitigation: connection can be disabled by any org admin + break-glass emergency access.
|
||
|
|
- Risk: role escalation by incorrect claim mapping.
|
||
|
|
- Mitigation: simple mapping validation + default least privilege (`ROLE.STUDENT`).
|
||
|
|
- Risk: callback replay attacks.
|
||
|
|
- Mitigation: one-time state, timestamp windows, assertion ID tracking.
|
||
|
|
|
||
|
|
## Finalized Decisions
|
||
|
|
1. **OIDC only for v1** - SAML deferred to v2 (Better Auth SAML in active development).
|
||
|
|
2. Use **Better Auth SSO plugin** (`@better-auth/sso`) for OIDC flow handling.
|
||
|
|
3. Break-glass local admin access is included with strict auditing.
|
||
|
|
4. Domain enforcement simplified to auto-join toggle.
|
||
|
|
5. SSO is gated to `PLAN.ENTERPRISE`.
|
||
|
|
6. SCIM deferred to future phase.
|
||
|
|
7. Single connection per org for v1.
|
||
|
|
8. Standard claims only, no custom claim mapping UI.
|