Single Sign-On (SSO) Configuration¶
Configure Google and Microsoft SSO for your AuthVital instance.
Overview¶
AuthVital supports SSO with: - Google - Google Workspace and personal accounts - Microsoft - Azure AD, Microsoft 365, personal Microsoft accounts
SSO can be configured at two levels: 1. Instance-level: Default SSO for all tenants 2. Tenant-level: Custom SSO per tenant (overrides instance defaults)
Architecture¶
┌─────────────────────────────────────────────────────────────────────────────┐
│ User clicks "Sign in with Google" │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ AuthVital checks tenant SSO config │
│ ├─ Tenant has custom config? → Use tenant credentials │
│ └─ No custom config? → Use instance-level credentials │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Redirect to Google/Microsoft OAuth │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ User authenticates with Google/Microsoft │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ AuthVital receives OAuth callback │
│ ├─ User exists? → Log them in │
│ ├─ Email matches existing user? → Link accounts (if autoLinkExisting) │
│ └─ New user? → Create account (if autoCreateUser) │
└─────────────────────────────────────────────────────────────────────────────┘
Google SSO Setup¶
1. Create Google Cloud Project¶
- Go to Google Cloud Console
- Create a new project or select existing
- Enable the Google+ API (for profile info)
2. Configure OAuth Consent Screen¶
- Go to APIs & Services → OAuth consent screen
- Choose user type:
- Internal: Only your Google Workspace users
- External: Any Google account
- Fill in app information:
- App name: Your app name
- User support email: Your email
- Authorized domains:
yourapp.com,yourauthvital.com - Add scopes:
openidemailprofile
3. Create OAuth Credentials¶
- Go to APIs & Services → Credentials
- Click Create Credentials → OAuth client ID
- Application type: Web application
- Add authorized redirect URIs:
- Save the Client ID and Client Secret
4. Configure in AuthVital¶
Via Admin Panel:
- Go to Settings → SSO
- Select Google
- Enter:
- Client ID
- Client Secret
- Allowed domains (optional, e.g.,
yourcompany.com) - Enable and save
Via API:
await authvital.admin.configureSso({
provider: 'GOOGLE',
enabled: true,
clientId: 'your-google-client-id',
clientSecret: 'your-google-client-secret',
scopes: ['openid', 'email', 'profile'],
allowedDomains: ['yourcompany.com'], // Optional: restrict to domain
autoCreateUser: true, // Create new users automatically
autoLinkExisting: true, // Link to existing accounts with same email
});
Microsoft SSO Setup¶
1. Register App in Azure AD¶
- Go to Azure Portal
- Navigate to Azure Active Directory → App registrations
- Click New registration
- Configure:
- Name: Your app name
- Supported account types:
- Single tenant: Only your organization
- Multitenant: Any Azure AD organization
- Multitenant + personal: Azure AD + personal Microsoft accounts
- Redirect URI:
https://auth.yourapp.com/api/sso/microsoft/callback - Click Register
2. Configure Authentication¶
- Go to Authentication
- Under Platform configurations, verify Web redirect URI
- Enable ID tokens and Access tokens
3. Create Client Secret¶
- Go to Certificates & secrets
- Click New client secret
- Set description and expiration
- Copy the Value immediately (shown only once!)
4. Note Your IDs¶
From Overview tab, note: - Application (client) ID - Directory (tenant) ID (if single-tenant)
5. Configure in AuthVital¶
Via Admin Panel:
- Go to Settings → SSO
- Select Microsoft
- Enter:
- Client ID (Application ID)
- Client Secret
- Tenant ID (optional, for single-tenant)
- Allowed domains (optional)
- Enable and save
Via API:
await authvital.admin.configureSso({
provider: 'MICROSOFT',
enabled: true,
clientId: 'your-azure-app-id',
clientSecret: 'your-azure-client-secret',
// For multi-tenant, use 'common' or 'organizations' or 'consumers'
// For single-tenant, use your directory/tenant ID
scopes: ['openid', 'email', 'profile', 'User.Read'],
allowedDomains: ['yourcompany.com'],
autoCreateUser: true,
autoLinkExisting: true,
});
Tenant-Level SSO¶
Tenants can configure their own SSO credentials:
Use Case¶
- Enterprise customers with their own Azure AD
- Organizations wanting isolated identity management
- Different SSO providers per tenant
Configuration¶
Via Tenant Admin Panel:
- Tenant admin goes to Settings → SSO
- Configures their own OAuth credentials
- Can optionally enforce SSO (disable password login)
Via API:
await authvital.tenants.configureSso('tenant-id', {
provider: 'MICROSOFT',
enabled: true,
clientId: 'tenant-specific-azure-app-id',
clientSecret: 'tenant-specific-secret',
enforced: true, // Disable password login for this tenant
allowedDomains: ['tenant-company.com'],
});
Enforcement¶
When SSO is enforced for a tenant: - Password login is disabled - Users must use SSO - Password reset is disabled - Only SSO-linked users can access
// Check if tenant has enforced SSO
const ssoConfig = await authvital.tenants.getSsoConfig('tenant-id', 'MICROSOFT');
if (ssoConfig.enforced) {
// Hide password login, show only SSO button
}
Domain Restrictions¶
Restrict SSO to specific email domains:
// Instance-level: only allow company emails
await authvital.admin.configureSso({
provider: 'GOOGLE',
allowedDomains: ['yourcompany.com', 'subsidiary.com'],
// ...
});
// Tenant-level: restrict to tenant's domain
await authvital.tenants.configureSso('tenant-id', {
provider: 'MICROSOFT',
allowedDomains: ['tenant-company.com'],
// ...
});
If a user tries to SSO with an email outside allowed domains, they'll see an error.
Account Linking¶
Auto-Link Existing Accounts¶
⚠️ Security Warning: Auto-linking is now opt-in
The
autoLinkExistingoption now defaults tofalsefor security reasons. Enabling auto-linking can expose your application to account takeover attacks: if an attacker controls an SSO account (e.g., Google or Microsoft) with the same email address as an unverified AuthVital account, they could gain access to that account by simply signing in via SSO. Only enableautoLinkExisting: trueif you trust your SSO provider's email verification AND require email verification on AuthVital accounts.
When autoLinkExisting: true (opt-in):
- User signs in with Google/Microsoft
- AuthVital checks for existing user with same email
- If found, links the SSO identity to existing account
- User can now sign in via password OR SSO
Manual Linking¶
Users can link SSO accounts from their profile:
// Get available SSO providers
const providers = await authvital.sso.getAvailableProviders();
// [{ provider: 'GOOGLE', enabled: true }, { provider: 'MICROSOFT', enabled: true }]
// Initiate linking (returns OAuth URL)
const { url } = await authvital.sso.initiateLink(req, {
provider: 'GOOGLE',
redirectUri: 'https://yourapp.com/settings/account',
});
// Redirect user to url
Unlinking SSO¶
// Remove SSO link (user must have password set)
await authvital.sso.unlink(req, 'GOOGLE');
// Or for Microsoft:
await authvital.sso.unlink(req, 'MICROSOFT');
SSO Buttons¶
Important: SSO provider information should come from your backend. The available providers are fetched server-side using the AuthVital SDK.
Instance SSO Buttons¶
Show SSO options on login page:
import { useState, useEffect } from 'react';
import { useAuth } from '@authvital/sdk/client';
// NOTE: SSO provider information comes from your backend.
// Server-side (your API route):
// const providers = await authvital.sso.getAvailableProviders();
//
// For SSO login, redirect to the provider URL:
// const loginUrl = await authvital.sso.getLoginUrl(provider, { redirectUri, ... });
function LoginPage() {
const [ssoProviders, setSsoProviders] = useState([]);
useEffect(() => {
// Fetch available SSO providers from YOUR backend
fetch('/api/sso/providers')
.then(res => res.json())
.then(data => setSsoProviders(data.providers));
}, []);
return (
<div>
<LoginForm />
{ssoProviders.length > 0 && (
<>
<Divider>or continue with</Divider>
<SsoButtons providers={ssoProviders} />
</>
)}
</div>
);
}
function SsoButtons({ providers }) {
const handleSsoLogin = async (provider: string) => {
// Get SSO login URL from YOUR backend
const response = await fetch(`/api/sso/login-url?provider=${provider}`);
const { loginUrl } = await response.json();
window.location.href = loginUrl;
};
return (
<div className="sso-buttons">
{providers.includes('GOOGLE') && (
<button onClick={() => handleSsoLogin('google')}>
<GoogleIcon /> Sign in with Google
</button>
)}
{providers.includes('MICROSOFT') && (
<button onClick={() => handleSsoLogin('microsoft')}>
<MicrosoftIcon /> Sign in with Microsoft
</button>
)}
</div>
);
}
Tenant-Specific SSO¶
For tenant-scoped login pages, you'll need a backend endpoint to fetch SSO config:
Backend (using SDK):
import { createAuthVital } from '@authvital/sdk/server';
const authvital = createAuthVital({ /* config */ });
// Public endpoint to get tenant SSO info
app.get('/api/tenants/:slug/sso-info', async (req, res) => {
try {
const providers = await authvital.sso.getProvidersForTenant(req.params.slug);
res.json(providers);
} catch (error) {
res.status(404).json({ error: 'Tenant not found' });
}
});
Frontend:
import { useState, useEffect } from 'react';
function TenantLoginPage({ tenantSlug }: { tenantSlug: string }) {
const [ssoProviders, setSsoProviders] = useState<Array<{ provider: string; enforced: boolean }>>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchSsoConfig = async () => {
try {
const response = await fetch(`/api/tenants/${tenantSlug}/sso-info`);
if (!response.ok) throw new Error('Failed to fetch SSO config');
const providers = await response.json();
setSsoProviders(providers);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
};
fetchSsoConfig();
}, [tenantSlug]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
const enforcedProvider = ssoProviders.find(p => p.enforced);
// If SSO is enforced, show only SSO button
if (enforcedProvider) {
return (
<div>
<h1>Sign in to {tenantSlug}</h1>
<button onClick={() => loginWithSso(enforcedProvider.provider.toLowerCase(), tenantSlug)}>
Sign in with {enforcedProvider.provider}
</button>
</div>
);
}
// Otherwise show both options
return (
<div>
<LoginForm />
{ssoProviders.map(({ provider }) => (
<button
key={provider}
onClick={() => loginWithSso(provider.toLowerCase(), tenantSlug)}
>
Sign in with {provider}
</button>
))}
</div>
);
}
Security Considerations¶
✅ Best Practices¶
- Use domain restrictions - Only allow expected email domains
- Enable auto-link carefully - Consider security implications
- Rotate client secrets - Especially for Microsoft (they expire)
- Use internal consent - For enterprise Google Workspace
- Monitor SSO usage - Track failed attempts
❌ Avoid¶
- Don't share credentials - Each tenant should have own credentials if needed
- Don't skip domain validation - Prevents account takeover
- Don't disable MFA - SSO doesn't replace MFA
Troubleshooting¶
"Invalid redirect URI"¶
- Check redirect URI matches exactly (including trailing slashes)
- Ensure protocol matches (https vs http)
- For Google: Verify in Cloud Console → Credentials
- For Microsoft: Verify in Azure → Authentication
"Account not found"¶
- Check
autoCreateUseris enabled - Verify email domain is allowed
- Check if user already exists with different email
"Unable to link account"¶
- User must have password set to unlink SSO
- Check for existing SSO link for same provider
- Verify email matches between accounts
Microsoft: "AADSTS50011"¶
Reply URL mismatch. Ensure Azure AD registration has exact redirect URI.
Google: "Access blocked"¶
- OAuth consent screen not configured
- App not verified (for external users)
- Scopes not approved