An enterprise customer configures Okta SSO for their organization. Their IT team mandates that all employees must log in through Okta, which enforces their MFA policy. Six months later, their security team discovers that every user account in your application still has a password, and your login form still accepts password authentication for those email addresses. The entire investment in SSO enforcement was security theatre — any of those accounts could have been accessed with a password that was set before SSO was configured, and your application would have accepted it without any Okta involvement.
Why this happens
The typical product timeline: you build email/password auth, ship it. Later you add SSO. The SSO code adds a new authentication path but leaves the password path fully intact. Nobody goes back to add enforcement logic because "SSO is just another login option." This is the default state for most SaaS products that add SSO without explicitly considering enforcement.
Enterprise IT administrators assume that enabling SSO enforcement in your product means password login is disabled for their domain. When your product does not actually enforce this, they have no way to know — they test SSO works, it works, and they assume the enforcement is in place. The misalignment only surfaces during a security audit or after an incident.
Implementing SSO enforcement policy
// Org-level SSO configuration
interface OrgSSOConfig {
orgId: string;
ssoEnabled: boolean;
// 'optional': SSO works but password login still allowed (default for most orgs)
// 'required': SSO is the only login method — password login blocked
// 'required_except_admins': SSO required but org owners can still use password
enforcement: 'optional' | 'required' | 'required_except_admins';
idpType: 'saml' | 'oidc';
idpEntityId: string;
allowedDomains: string[];
}
// Login attempt: check SSO enforcement before processing password
async function checkSSOEnforcement(email: string): Promise<void> {
const domain = email.split('@')[1]?.toLowerCase();
if (!domain) return;
// Find any org that owns this email domain and has SSO enforcement
const ssoConfig = await db.orgSSOConfigs.findOne({
allowedDomains: { has: domain },
ssoEnabled: true,
enforcement: { in: ['required', 'required_except_admins'] },
});
if (!ssoConfig) return; // No enforcement applies
if (ssoConfig.enforcement === 'required') {
throw new SSOEnforcementError(ssoConfig.orgId, domain);
}
if (ssoConfig.enforcement === 'required_except_admins') {
const user = await db.users.findByEmail(email);
if (user) {
const membership = await db.orgMembers.findOne({
userId: user.id,
orgId: ssoConfig.orgId,
});
if (membership?.role !== 'owner') {
throw new SSOEnforcementError(ssoConfig.orgId, domain);
}
// Org owner: allow password login with a warning
}
}
}
app.post('/auth/login', async (req, res) => {
const { email, password } = req.body;
try {
await checkSSOEnforcement(email);
} catch (err) {
if (err instanceof SSOEnforcementError) {
return res.status(403).json({
error: 'sso_required',
message: 'Your organization requires SSO login.',
ssoUrl: `/sso/start?domain=${encodeURIComponent(err.domain)}`,
});
}
throw err;
}
// Proceed with password validation
});
Emergency bypass accounts
Enterprise IT teams legitimately need a way to access the application if their IdP goes down. Okta outages, Azure AD issues, and misconfigured SAML metadata can all prevent SSO-only users from logging in. Emergency bypass accounts are a real operational requirement.
The secure way to implement them is as break-glass accounts: accounts that exist outside the normal user directory, with complex random passwords stored in a password manager, with MFA enrolled, and with every login attempt generating an immediate alert to the security team. These accounts should have the minimum necessary permissions and should be audited quarterly for whether they have been used.
// Break-glass account properties
interface BreakGlassAccount {
id: string;
email: string; // Typically an ops@ address, not in the main domain
orgId: string;
role: 'admin';
isBreakGlass: true;
mfaRequired: true;
lastUsed: Date | null;
createdBy: string;
// Exempt from SSO enforcement — always allows password
ssoEnforcementExempt: true;
}
// Alert on every break-glass login
eventBus.subscribe('auth.login_success', async (event) => {
const user = await db.users.findById(event.userId);
if ((user as BreakGlassAccount).isBreakGlass) {
await alerting.critical({
title: 'Break-glass account used',
body: `${user.email} logged in at ${new Date().toISOString()} from IP ${event.ip}`,
channels: ['security-alerts', 'pagerduty'],
});
}
});
Test accounts and residual risk
Test accounts created during implementation or QA often have simple passwords and full access. When SSO enforcement goes in, these accounts are exempt from it (because their email domain is not the corporate domain) and persist indefinitely. They represent a permanently open password-login door into a potentially admin-privileged account.
Audit test accounts regularly. Create a policy that test accounts expire after 30 days. Use a dedicated domain for test accounts (@test.example.com) so they can be identified and bulk-cleaned up. Require even test accounts to use MFA. Remove test account admin roles before accounts leave the development cycle.
The enforcement migration path
For existing orgs that have users with passwords and want to migrate to SSO-required enforcement, the transition needs care. The day you flip the enforcement switch, users who have not yet logged in via SSO will be locked out of the password path. Run a pre-migration check to confirm all org members have completed at least one SSO login before enforcing.
async function canEnforceSSOForOrg(orgId: string): Promise<{
canEnforce: boolean;
nonSSOUsers: string[];
}> {
const members = await db.orgMembers.findMany({ orgId });
const nonSSOUsers = [];
for (const member of members) {
const hasSSOLogin = await db.loginHistory.exists({
userId: member.userId,
method: 'sso',
});
if (!hasSSOLogin) {
nonSSOUsers.push(member.userId);
}
}
return {
canEnforce: nonSSOUsers.length === 0,
nonSSOUsers,
};
}