SSO & Identity Providers
OIDC/OAuth2 SSO architecture, PKCE flow, identity provider model, external login linking, OAuth applications, claim mapping, and full API reference.
SCRIPE supports Single Sign-On (SSO) via external OIDC identity providers. This page covers the complete SSO architecture: PKCE authorization code flow, the identity provider entity model, external login linking, OAuth application registration, claim mapping, tenant scoping, and all API endpoints.
How SSO Works
SCRIPE uses the Authorization Code Flow with PKCE (Proof Key for Code Exchange) for SSO. This is the most secure OAuth2 flow, recommended by the OAuth 2.1 specification for all client types.
1. Provider Discovery
The login page fetches available SSO providers via GET /auth/oidc/providers/admin. Only enabled providers for the current login context (admin/user) are returned.2. PKCE Challenge
When the user clicks an SSO button, the frontend calls POST /auth/oidc/challenge. The backend generates a code_verifier, computes the code_challenge (SHA-256), and returns the authorization URL.3. IdP Redirect
The frontend stores PKCE state (code_verifier, state, providerId) in sessionStorage, then redirects the user to the external IdP's authorization endpoint.4. User Authentication
The user authenticates at the external IdP (Azure AD, Google, Okta, etc.) and grants consent for the requested scopes.5. Callback & Token Exchange
The IdP redirects to /sso/callback with an authorization code. The frontend retrieves PKCE state from sessionStorage, validates the state parameter, and calls POST /auth/oidc/callback. The backend exchanges the code for tokens using the code_verifier.
PKCE Security Model
PKCE prevents authorization code interception attacks by ensuring that only the client that initiated the flow can exchange the code. The code_verifier is never sent over the network — only its SHA-256 hash (code_challenge) is sent during the challenge step.
| Component | Purpose |
|---|---|
| code_verifier | Cryptographically random string stored in sessionStorage |
| code_challenge | SHA-256 hash of the code_verifier sent to the IdP |
| state | CSRF protection — verified on callback |
| Authorization Code | One-time code from IdP, exchanged server-side |
Identity Provider Entity
The IdentityProvider entity stores all OIDC configuration for an external IdP. Each provider is optionally scoped to a tenant (TenantId = null means system-wide).
| Field | Type | Description |
|---|---|---|
| Name | string(100) | Human-readable provider name (e.g., 'Corporate Azure AD') |
| Slug | string(50) | URL-safe identifier, unique per tenant |
| Protocol | string(30) | Authentication protocol: oidc, oauth2, saml (future) |
| TenantId | Guid? | Null = system-wide; set = tenant-specific provider |
| Authority | string(500) | OIDC issuer URL for discovery document |
| ClientId | string(200) | OAuth2 client_id registered with the provider |
| ClientSecretEncrypted | string(500) | AES-encrypted secret, never returned to frontend |
| Scopes | string(1000) | OAuth2 scopes (default: openid profile email) |
| ClaimMappingJson | string(2000) | JSON mapping external claims to SCRIPE claims |
| EnabledForAdmins | bool | Show on admin login page |
| EnabledForUsers | bool | Show on user login page |
| ButtonColor | string(50) | CSS color for branded login button |
| ButtonLabel | string(200) | Custom button text |
| DisplayOrder | int | Sort order on the login page |
External Login Linking
Before SSO login works, a SCRIPE admin/user must link their account to the external identity. This creates an ExternalLogin record mapping the provider's subject ID to the SCRIPE account.
- Admin signs in with username/password
- Navigates to Profile → Security → External Logins
- Clicks 'Link External Account' for the desired provider
- Completes OIDC authentication with the external IdP
- SCRIPE creates an ExternalLogin record linking the provider identity to the admin
- From now on, the admin can sign in using the SSO button on the login page
OAuth Applications
OAuth Applications are third-party apps that authenticate against SCRIPE as an OIDC server. Each app gets a Client ID and Client Secret, with configurable redirect URIs, scopes, and PKCE enforcement.
| Field | Description |
|---|---|
| Application Name | Display name for the third-party app |
| Client ID | Auto-generated unique identifier |
| Client Secret | Auto-generated secret (shown only once on creation) |
| Client Type | Confidential (server-side) or Public (SPA/mobile) |
| Redirect URIs | Allowed callback URLs |
| Allowed Scopes | openid, profile, email, roles (configurable) |
| PKCE Required | Enforce Proof Key for Code Exchange |
Claim Mapping
When an external IdP uses non-standard claim names, the ClaimMappingJson field maps them to SCRIPE's expected claims. If null, standard OIDC claim names (sub, email, name) are used.
{
"sub": "oid",
"email": "preferred_username",
"name": "display_name",
"given_name": "first_name",
"family_name": "last_name"
}Tenant Scoping
Identity Providers are tenant-scoped via the TenantId column. Providers with null TenantId are system-wide (available to all tenants). The backend automatically filters providers by the current admin's tenant context.
API Endpoints
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/v1/auth/oidc/providers/admin | List SSO providers for admin login | 🔒 Required |
| GET | /api/v1/auth/oidc/providers/user | List SSO providers for user login | 🔒 Required |
| POST | /api/v1/auth/oidc/challenge | Generate PKCE authorization URL | 🔒 Required |
| POST | /api/v1/auth/oidc/callback | Exchange auth code for linked account | 🔒 Required |
| GET | /api/v1/IdentityProviders | List identity providers (CRUD) | 🔒 Required |
| POST | /api/v1/IdentityProviders | Create identity provider | 🔒 Required |
| PUT | /api/v1/IdentityProviders/{id} | Update identity provider | 🔒 Required |
| DELETE | /api/v1/IdentityProviders/{id} | Delete identity provider | 🔒 Required |
| POST | /api/v1/IdentityProviders/{id}/test | Test OIDC connection | 🔒 Required |
| GET | /api/v1/OAuthApplications | List OAuth applications (CRUD) | 🔒 Required |
| POST | /api/v1/OAuthApplications | Create OAuth application | 🔒 Required |
| POST | /api/v1/OAuthApplications/{id}/regenerate-secret | Regenerate client secret | 🔒 Required |