mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-08 20:37:44 +00:00
feat(sso): defaultSSO options and ACS endpoint (#3660)
Co-authored-by: Bereket Engida <Bekacru@gmail.com> Co-authored-by: Bereket Engida <86073083+Bekacru@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
a208c09894
commit
b3ead859e6
174
docs/content/docs/guides/saml-sso-with-okta.mdx
Normal file
174
docs/content/docs/guides/saml-sso-with-okta.mdx
Normal file
@@ -0,0 +1,174 @@
|
||||
---
|
||||
title: SAML SSO with Okta
|
||||
description: A guide to integrating SAML Single Sign-On (SSO) with Better Auth, featuring Okta
|
||||
---
|
||||
|
||||
This guide walks you through setting up SAML Single Sign-On (SSO) with your Identity Provider (IdP), using Okta as an example. For advanced configuration details and the full API reference, check out the [SSO Plugin Documentation](/docs/plugins/sso).
|
||||
|
||||
## What is SAML?
|
||||
|
||||
SAML (Security Assertion Markup Language) is an XML-based standard for exchanging authentication and authorization data between an Identity Provider (IdP) (e.g., Okta, Azure AD, OneLogin) and a Service Provider (SP) (in this case, Better Auth).
|
||||
|
||||
In this setup:
|
||||
|
||||
- **IdP (Okta)**: Authenticates users and sends assertions about their identity.
|
||||
- **SP (Better Auth)**: Validates assertions and logs the user in.up.
|
||||
|
||||
### Step 1: Create a SAML Application in Okta
|
||||
|
||||
1. Log in to your Okta Admin Console
|
||||
2. Navigate to Applications > Applications
|
||||
3. Click "Create App Integration"
|
||||
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
|
||||
- **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
|
||||
|
||||
### Step 2: Configure Better Auth
|
||||
|
||||
Here’s an example configuration for Okta in a dev environment:
|
||||
|
||||
```typescript
|
||||
const ssoConfig = {
|
||||
defaultSSO: [{
|
||||
domain: "localhost:3000", // Your domain
|
||||
providerId: "sso",
|
||||
samlConfig: {
|
||||
// SP Configuration
|
||||
issuer: "http://localhost:3000/api/auth/sso/saml2/sp/metadata",
|
||||
entryPoint: "https://trial-1076874.okta.com/app/trial-1076874_samltest_1/exktofb0a62hqLAUL697/sso/saml",
|
||||
callbackUrl: "/dashboard", // Redirect after successful authentication
|
||||
|
||||
// IdP Configuration
|
||||
idpMetadata: {
|
||||
entityID: "https://trial-1076874.okta.com/app/exktofb0a62hqLAUL697/sso/saml/metadata",
|
||||
singleSignOnService: [{
|
||||
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
||||
Location: "https://trial-1076874.okta.com/app/trial-1076874_samltest_1/exktofb0a62hqLAUL697/sso/saml"
|
||||
}],
|
||||
cert: `-----BEGIN CERTIFICATE-----
|
||||
MIIDqjCCApKgAwIBAgIGAZhVGMeUMA0GCSqGSIb3DQEBCwUAMIGVMQswCQYDVQQGEwJVUzETMBEG
|
||||
...
|
||||
[Your Okta Certificate]
|
||||
...
|
||||
-----END CERTIFICATE-----`
|
||||
},
|
||||
|
||||
// SP Metadata
|
||||
spMetadata: {
|
||||
metadata: `<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||
entityID="http://localhost:3000/api/sso/saml2/sp/metadata">
|
||||
...
|
||||
[Your SP Metadata XML]
|
||||
...
|
||||
</md:EntityDescriptor>`
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Multiple Default Providers (Optional)
|
||||
|
||||
You can configure multiple SAML providers for different domains:
|
||||
|
||||
```typescript
|
||||
const ssoConfig = {
|
||||
defaultSSO: [
|
||||
{
|
||||
domain: "company.com",
|
||||
providerId: "company-okta",
|
||||
samlConfig: {
|
||||
// Okta SAML configuration for company.com
|
||||
}
|
||||
},
|
||||
{
|
||||
domain: "partner.com",
|
||||
providerId: "partner-adfs",
|
||||
samlConfig: {
|
||||
// ADFS SAML configuration for partner.com
|
||||
}
|
||||
},
|
||||
{
|
||||
domain: "contractor.org",
|
||||
providerId: "contractor-azure",
|
||||
samlConfig: {
|
||||
// Azure AD SAML configuration for contractor.org
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
**Explicit**: Pass providerId directly when signing in.
|
||||
**Domain fallback:** Matches based on the user’s email domain. e.g. user@company.com → matches `company-okta` provider.
|
||||
</Callout>
|
||||
|
||||
|
||||
### Step 4: Initiating Sign-In
|
||||
|
||||
You can start an SSO flow in three ways:
|
||||
|
||||
**1. Explicitly by `providerId` (recommended):**
|
||||
|
||||
```typescript
|
||||
// Explicitly specify which provider to use
|
||||
await authClient.signIn.sso({
|
||||
providerId: "company-okta",
|
||||
callbackURL: "/dashboard"
|
||||
});
|
||||
```
|
||||
|
||||
**2. By email domain matching:**
|
||||
|
||||
```typescript
|
||||
// Automatically matches provider based on email domain
|
||||
await authClient.signIn.sso({
|
||||
email: "user@company.com",
|
||||
callbackURL: "/dashboard"
|
||||
});
|
||||
```
|
||||
|
||||
**3. By specifying domain:**
|
||||
|
||||
```typescript
|
||||
// Explicitly specify domain for matching
|
||||
await authClient.signIn.sso({
|
||||
domain: "partner.com",
|
||||
callbackURL: "/dashboard"
|
||||
});
|
||||
```
|
||||
|
||||
**Important Notes**:
|
||||
- DummyIDP should ONLY be used for development and testing
|
||||
- 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
|
||||
|
||||
### Step 5: Dynamically Registering SAML Providers
|
||||
|
||||
For dynamic registration, you should register SAML providers using the API. See the [SSO Plugin Documentation](/docs/plugins/sso#register-a-saml-provider) for detailed registration instructions.
|
||||
|
||||
Example registration:
|
||||
|
||||
```typescript
|
||||
await authClient.sso.register({
|
||||
providerId: "okta-prod",
|
||||
issuer: "https://your-domain.com",
|
||||
domain: "your-domain.com",
|
||||
samlConfig: {
|
||||
// Your production SAML configuration
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [SSO Plugin Documentation](/docs/plugins/sso)
|
||||
- [Okta SAML Documentation](https://developer.okta.com/docs/concepts/saml/)
|
||||
- [SAML 2.0 Specification](https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf)
|
||||
@@ -7,11 +7,6 @@ description: Integrate Single Sign-On (SSO) with your application.
|
||||
|
||||
Single Sign-On (SSO) allows users to authenticate with multiple applications using a single set of credentials. This plugin supports OpenID Connect (OIDC), OAuth2 providers, and SAML 2.0.
|
||||
|
||||
|
||||
<Callout type="warn">
|
||||
This plugin is in active development and may not be suitable for production use. Please report any issues or bugs on [GitHub](https://github.com/better-auth/better-auth) and any security concerns on [security@better-auth.com](mailto:security@better-auth.com).
|
||||
</Callout>
|
||||
|
||||
## Installation
|
||||
|
||||
<Steps>
|
||||
@@ -100,14 +95,18 @@ await authClient.sso.register({
|
||||
discoveryEndpoint: "https://idp.example.com/.well-known/openid-configuration",
|
||||
scopes: ["openid", "email", "profile"],
|
||||
pkce: true,
|
||||
},
|
||||
mapping: {
|
||||
id: "sub",
|
||||
email: "email",
|
||||
emailVerified: "email_verified",
|
||||
name: "name",
|
||||
image: "picture",
|
||||
},
|
||||
mapping: {
|
||||
id: "sub",
|
||||
email: "email",
|
||||
emailVerified: "email_verified",
|
||||
name: "name",
|
||||
image: "picture",
|
||||
extraFields: {
|
||||
department: "department",
|
||||
role: "role"
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
@@ -129,14 +128,18 @@ await auth.api.registerSSOProvider({
|
||||
discoveryEndpoint: "https://idp.example.com/.well-known/openid-configuration",
|
||||
scopes: ["openid", "email", "profile"],
|
||||
pkce: true,
|
||||
},
|
||||
mapping: {
|
||||
id: "sub",
|
||||
email: "email",
|
||||
emailVerified: "email_verified",
|
||||
name: "name",
|
||||
image: "picture",
|
||||
},
|
||||
mapping: {
|
||||
id: "sub",
|
||||
email: "email",
|
||||
emailVerified: "email_verified",
|
||||
name: "name",
|
||||
image: "picture",
|
||||
extraFields: {
|
||||
department: "department",
|
||||
role: "role"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
headers,
|
||||
});
|
||||
@@ -183,19 +186,20 @@ await authClient.sso.register({
|
||||
isAssertionEncrypted: true,
|
||||
encPrivateKey: "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----",
|
||||
encPrivateKeyPass: "your-sp-encryption-key-password"
|
||||
},
|
||||
mapping: {
|
||||
id: "nameID",
|
||||
email: "email",
|
||||
name: "displayName",
|
||||
firstName: "givenName",
|
||||
lastName: "surname",
|
||||
emailVerified: "email_verified",
|
||||
extraFields: {
|
||||
department: "department",
|
||||
role: "role"
|
||||
}
|
||||
}
|
||||
},
|
||||
mapping: {
|
||||
id: "nameID",
|
||||
email: "email",
|
||||
name: "displayName",
|
||||
firstName: "givenName",
|
||||
lastName: "surname",
|
||||
extraFields: {
|
||||
department: "department",
|
||||
role: "role"
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
@@ -233,19 +237,20 @@ await auth.api.registerSSOProvider({
|
||||
isAssertionEncrypted: true,
|
||||
encPrivateKey: "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----",
|
||||
encPrivateKeyPass: "your-sp-encryption-key-password"
|
||||
},
|
||||
mapping: {
|
||||
id: "nameID",
|
||||
email: "email",
|
||||
name: "displayName",
|
||||
firstName: "givenName",
|
||||
lastName: "surname",
|
||||
emailVerified: "email_verified",
|
||||
extraFields: {
|
||||
department: "department",
|
||||
role: "role"
|
||||
}
|
||||
}
|
||||
},
|
||||
mapping: {
|
||||
id: "nameID",
|
||||
email: "email",
|
||||
name: "displayName",
|
||||
firstName: "givenName",
|
||||
lastName: "surname",
|
||||
extraFields: {
|
||||
department: "department",
|
||||
role: "role"
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
headers,
|
||||
});
|
||||
@@ -611,6 +616,36 @@ organizationProvisioning: {
|
||||
|
||||
## SAML Configuration
|
||||
|
||||
|
||||
### Default SSO Provider
|
||||
|
||||
```ts title="auth.ts"
|
||||
const auth = betterAuth({
|
||||
plugins: [
|
||||
sso({
|
||||
defaultSSO: {
|
||||
providerId: "default-saml", // Provider ID for the default provider
|
||||
samlConfig: {
|
||||
issuer: "https://your-app.com",
|
||||
entryPoint: "https://idp.example.com/sso",
|
||||
cert: "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
|
||||
callbackUrl: "http://localhost:3000/api/auth/sso/saml2/sp/acs",
|
||||
spMetadata: {
|
||||
entityID: "http://localhost:3000/api/auth/sso/saml2/sp/metadata",
|
||||
metadata: "<!-- Your SP Metadata XML -->",
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
The defaultSSO provider will be used when:
|
||||
1. No matching provider is found in the database
|
||||
|
||||
This allows you to test SAML authentication without setting up providers in the database. The defaultSSO provider supports all the same configuration options as regular SAML providers.
|
||||
|
||||
### Service Provider Configuration
|
||||
|
||||
When registering a SAML provider, you need to provide Service Provider (SP) metadata configuration:
|
||||
@@ -679,6 +714,8 @@ The plugin requires additional fields in the `ssoProvider` table to store the pr
|
||||
]}
|
||||
/>
|
||||
|
||||
For a detailed guide on setting up SAML SSO with examples for Okta and testing with DummyIDP, see our [SAML SSO Setup Guide](/docs/guides/sso-saml-guide).
|
||||
|
||||
## Options
|
||||
|
||||
### Server
|
||||
@@ -735,5 +772,31 @@ The plugin requires additional fields in the `ssoProvider` table to store the pr
|
||||
type: "number | function",
|
||||
default: 10,
|
||||
},
|
||||
defaultSSO: {
|
||||
description: "Configure a default SSO provider for testing and development. This provider will be used when no matching provider is found in the database.",
|
||||
type: "object",
|
||||
properties: {
|
||||
domain: {
|
||||
description: "The domain to match for this default provider.",
|
||||
type: "string",
|
||||
required: true,
|
||||
},
|
||||
providerId: {
|
||||
description: "The provider ID to use for the default provider.",
|
||||
type: "string",
|
||||
required: true,
|
||||
},
|
||||
samlConfig: {
|
||||
description: "SAML configuration for the default provider.",
|
||||
type: "SAMLConfig",
|
||||
required: false,
|
||||
},
|
||||
oidcConfig: {
|
||||
description: "OIDC configuration for the default provider.",
|
||||
type: "OIDCConfig",
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user