diff --git a/docs/content/docs/guides/saml-sso-with-okta.mdx b/docs/content/docs/guides/saml-sso-with-okta.mdx index 460edcea7e..478144e7fb 100644 --- a/docs/content/docs/guides/saml-sso-with-okta.mdx +++ b/docs/content/docs/guides/saml-sso-with-okta.mdx @@ -22,12 +22,16 @@ In this setup: 4. Select "SAML 2.0" as the Sign-in method 5. Configure the following settings: - - **Single Sign-on URL**: Your Better Auth ACS endpoint (e.g., `http://localhost:3000/api/auth/sso/saml2/sp/acs/sso`). while `sso` being your providerId + - **Single Sign-on URL**: Your Better Auth callback endpoint (e.g., `http://localhost:3000/api/auth/sso/saml2/callback/sso`). Note: `sso` is your `providerId` - **Audience URI (SP Entity ID)**: Your Better Auth metadata URL (e.g., `http://localhost:3000/api/auth/sso/saml2/sp/metadata`) - **Name ID format**: Email Address or any of your choice. 6. Download the IdP metadata XML file and certificate + +**IdP-Initiated SSO**: If you want users to access your app from the Okta dashboard, make sure the **Single Sign-on URL** points to the callback endpoint (`/api/auth/sso/saml2/callback/{providerId}`). Better Auth automatically handles both SP-initiated and IdP-initiated flows. + + ### Step 2: Configure Better Auth Here’s an example configuration for Okta in a dev environment: @@ -149,6 +153,7 @@ await authClient.signIn.sso({ - Never use these certificates in production - The example uses `localhost:3000` - adjust URLs for your environment - For production, always use proper IdP providers like Okta, Azure AD, or OneLogin + - The `callbackUrl` in your SAML config should point to your app's destination (e.g., `/dashboard`), not the callback route itself ### Step 5: Dynamically Registering SAML Providers diff --git a/docs/content/docs/plugins/sso.mdx b/docs/content/docs/plugins/sso.mdx index 070806a923..c88de966b1 100644 --- a/docs/content/docs/plugins/sso.mdx +++ b/docs/content/docs/plugins/sso.mdx @@ -1242,7 +1242,25 @@ type requestDomainVerification = { The plugin automatically creates the following SAML endpoints: - **SP Metadata**: `/api/auth/sso/saml2/sp/metadata?providerId={providerId}` -- **SAML Callback**: `/api/auth/sso/saml2/callback/{providerId}` +- **SAML Callback**: `/api/auth/sso/saml2/callback/{providerId}` (supports both GET and POST) + +### SAML Callback URL Configuration + +The SAML callback endpoint (`/api/auth/sso/saml2/callback/{providerId}`) handles both **SP-initiated** and **IdP-initiated** SSO flows: + +- **SP-initiated**: User clicks "Sign in with SSO" in your app → redirects to IdP → IdP POSTs SAMLResponse to callback +- **IdP-initiated**: User clicks app icon in IdP dashboard (Okta, Azure AD, etc.) → IdP POSTs SAMLResponse to callback + +**Important**: The `callbackUrl` in your SAML configuration should point to your application's destination URL (e.g., `/dashboard`), **not** the callback route itself. Better Auth automatically handles the callback route and redirects users to your specified `callbackUrl` after successful authentication. + +```ts +samlConfig: { + callbackUrl: "/dashboard", // Correct - points to your app destination + // callbackUrl: "/api/auth/sso/saml2/callback/my-provider" // Incorrect - don't point to callback route +} +``` + +The callback route supports both GET and POST methods automatically, so you don't need to create any additional route handlers in your framework. ## Schema @@ -1273,6 +1291,27 @@ The `ssoProvider` schema is extended as follows: ]} /> +### IdP-Initiated SAML SSO + +Better Auth supports **IdP-initiated SSO flows**, where users access your application directly from their Identity Provider dashboard (e.g., Okta, Azure AD, OneLogin). This is common in enterprise environments where IT admins prefer centralized app access. + +**How it works:** + +1. User clicks your app icon in the IdP dashboard +2. IdP POSTs SAMLResponse to `/api/auth/sso/saml2/callback/{providerId}` +3. Better Auth processes the assertion, creates a session, and redirects to your `callbackUrl` +4. Browser follows the redirect with a GET request (handled automatically) + +**No additional configuration required** - the callback route automatically handles both GET and POST requests. + + +If you previously created a manual GET handler for the SAML callback route as a workaround, you can remove it after upgrading. Better Auth now handles GET requests automatically. + + + +**Security:** Better Auth validates all redirect URLs to prevent open redirect attacks. Only relative paths (e.g., `/dashboard`) and URLs matching your configured `trustedOrigins` are allowed. Malicious URLs like `https://evil.com` or protocol-relative URLs (`//evil.com`) are automatically blocked. + + For a detailed guide on setting up SAML SSO with examples for Okta and testing with DummyIDP, see our [SAML SSO with Okta](/docs/guides/saml-sso-with-okta). ## Options diff --git a/packages/better-auth/src/api/index.ts b/packages/better-auth/src/api/index.ts index cebe6e77bb..d7142f50b7 100644 --- a/packages/better-auth/src/api/index.ts +++ b/packages/better-auth/src/api/index.ts @@ -6,6 +6,7 @@ import type { } from "@better-auth/core"; import type { InternalLogger } from "@better-auth/core/env"; import { logger } from "@better-auth/core/env"; +import { normalizePathname } from "@better-auth/core/utils/url"; import type { Endpoint, Middleware } from "better-call"; import { createRouter } from "better-call"; import type { UnionToIntersection } from "../types/helper"; @@ -275,14 +276,7 @@ export const router =