danny-avila/LibreChat · error · Error

Email domain not allowed

Error message

Email domain not allowed

What it means

The first of three `Email domain not allowed` throws in the OpenID strategy. It runs the domain allowlist check against the BASE application config (`baseConfig`, resolved with `{ baseOnly: true }`) before any user record is looked up. `isEmailDomainAllowed` returns `true` (allow) whenever `allowedDomains` is empty/null/missing; it only denies when a non-empty list is configured and the email's domain is not in it, or when there is no email at all. This throw is special-cased by `createOpenIDCallback` into `done(null, false, { message })`, so the user sees a 302 redirect to `/login?redirect=false&error=auth_failed`, not a 500.

Source

Thrown at api/strategies/openidStrategy.js:587

  const claims = tokenset.claims ? tokenset.claims() : tokenset;
  const userinfo = {
    ...claims,
  };

  if (tokenset.access_token) {
    const providerUserinfo = await getUserInfo(openidConfig, tokenset.access_token, claims.sub);
    Object.assign(userinfo, providerUserinfo);
  }

  const email = getOpenIdEmail(userinfo);
  const openidIssuer = getOpenIdIssuer(claims, openidConfig);

  const baseConfig = await getAppConfig({ baseOnly: true });
  if (!isEmailDomainAllowed(email, baseConfig?.registration?.allowedDomains)) {
    logger.error(
      `[OpenID Strategy] Authentication blocked - email domain not allowed [Identifier: ${email}]`,
    );
    throw new Error('Email domain not allowed');
  }

  const result = await findOpenIDUser({
    findUser,
    email: email,
    openidId: claims.sub || userinfo.sub,
    openidIssuer,
    idOnTheSource: claims.oid || userinfo.oid,
    strategyName: 'openidStrategy',
  });
  let user = result.user;
  const error = result.error;

  if (error) {
    throw new Error(ErrorTypes.AUTH_FAILED);
  }

  const appConfig = user?.tenantId ? await resolveAppConfigForUser(getAppConfig, user) : baseConfig;

View on GitHub (pinned to 5ff282f900)

Solutions

  1. Check the effective `registration.allowedDomains` in the app config (filter `AppConfig` in the DB or the config file) and confirm the user's email domain is listed.
  2. If the IdP does not send a standard `email` claim, set `OPENID_EMAIL_CLAIM` to the claim key that carries the address (e.g. `upn`, `preferred_username`, or a custom claim).
  3. If the policy is intended to be per-tenant/role only, leave base `allowedDomains` empty so this pre-check passes and the tenant-aware check at line 611 enforces the real policy.
  4. Verify the domain string exactly — matching is case-insensitive but exact; subdomains are NOT matched (`a.company.com` will not satisfy `company.com`).

Example fix

// before
if (!isEmailDomainAllowed(email, baseConfig?.registration?.allowedDomains)) {
  logger.error(`[OpenID Strategy] Authentication blocked - email domain not allowed [Identifier: ${email}]`);
  throw new Error('Email domain not allowed');
}
// after — include the domain list in the log so operators can self-diagnose
const allowed = baseConfig?.registration?.allowedDomains;
if (!isEmailDomainAllowed(email, allowed)) {
  logger.error(`[OpenID Strategy] Blocked ${email}: domain not in [${(allowed || []).join(', ') || 'allow-all'}]`);
  throw new Error('Email domain not allowed');
}
Defensive patterns

Strategy: validation

Validate before calling

// Resolve and inspect the effective base allowlist before going through SSO
const baseConfig = await getAppConfig({ baseOnly: true });
const allowed = baseConfig?.registration?.allowedDomains;
if (Array.isArray(allowed) && allowed.length && !allowed.includes(userEmail.split('@')[1]?.toLowerCase())) {
  throw new Error(`Email domain blocked at base-config level; allowed: ${allowed.join(', ')}`);
}

Type guard

function isAllowedDomainConfig(v: unknown): v is string[] {
  return Array.isArray(v) && v.every((d) => typeof d === 'string' && d.length > 0);
}

Try / catch

// In the OpenID callback authenticator, map 'Email domain not allowed' to a user-facing message
try {
  await processOpenIDAuth(tokenset, existingUsersOnly);
} catch (err) {
  if (err.message === 'Email domain not allowed') return done(null, false, { message: err.message });
  done(err);
}

Prevention

When it happens

Trigger: An OpenID/OIDC provider authenticates a user whose email domain is not in the configured `registration.allowedDomains` list. Concretely: `OPENID_*` env is set, the IdP callback succeeds, `getOpenIdEmail` returns an address like `user@contractor.external`, and `allowedDomains` contains `['company.com']`. Also triggered if `getOpenIdEmail` returns `undefined` (no email/upn/preferred_username claim) WHILE a domain list is configured.

Common situations: Operator added `allowedDomains` to lock the instance to corporate accounts but a personal/BYOD email slipped through; the IdP sends `preferred_username` instead of `email` and the configured `OPENID_EMAIL_CLAIM` doesn't match; multi-tenant setups where the base config is narrower than a tenant's override (the tenant-aware check at line 611 is the one that matters then, but this base check still fires first).

Related errors


AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12). Data as JSON: /api/errors/8f7ad49dc70481a3. Report an issue: GitHub.