Skip to content

feat(auth-providers): verified OIDC e-mail from ID-token claims, optional group allowlist and prompt - #11069

Open
MichaelUray wants to merge 2 commits into
hcengineering:developfrom
MichaelUray:feat/oidc-verified-email-groups
Open

MichaelUray wants to merge 2 commits into
hcengineering:developfrom
MichaelUray:feat/oidc-verified-email-groups

Conversation

@MichaelUray

Copy link
Copy Markdown
Contributor

Motivation

With identity providers such as Microsoft Entra ID, the userinfo endpoint often does not return email_verified, so OIDC users are created without a verified e-mail. Existing accounts are then not linked by e-mail, and ADMIN_EMAILS does not take effect. Deployments also have no way to restrict OIDC sign-in to members of particular groups.

Changes

Verified e-mail

  • The ID token's email claim is used when it is an e-mail address without # and the provider marks it verified (email_verified, including the case-insensitive string true, or Entra's xms_edov). These claims vouch only for the email claim, never for a preferred_username fallback. With OPENID_TRUST_EMAIL=true it is also accepted when the token's iss exactly matches OPENID_ISSUER and its tid matches the tenant in an Entra v2.0 issuer URL. This setting trusts tenant administrators because they can set mail and UPN attributes. Tenant trust never applies to B2B guests or accounts from another identity provider: a preferred_username or upn containing #EXT#, an idp claim that differs from iss, or acct=1 leaves the e-mail unverified, because the home directory controls that address.
  • A verified userinfo email is still used as a fallback.
  • An email claim that is not an e-mail address or contains # yields no ID-token candidate. preferred_username is only used as a candidate when the ID token has no email claim, and only when it is an e-mail address without #. The userinfo fallback is filtered the same way.

Group allowlist

  • OPENID_ALLOWED_GROUPS=group-a,group-b (optional) is matched against the groups claim of the ID token, case-insensitively. If the variable is unset or empty, behaviour is unchanged.
  • The allowlist fails closed. A missing groups claim, or a group-overage indication (_claim_names, _claim_sources, hasgroups), denies access.
  • The check runs before any account handling, so a denied user never gets an account, person or social id. They are sent to /login?oidcError=group_not_allowed, and the login page shows a localized message. The new string is added to every login-assets locale.
  • OPENID_SIGNUP_FOR_ALLOWED_GROUPS=true lets users who pass a non-empty allowlist sign up through OIDC even when signup is otherwise disabled. Password signup is not affected.

Prompt

  • OPENID_PROMPT (optional) passes select_account, login, consent or none to the authorization request. Combinations are allowed, except that none must be used alone. An invalid value is ignored, and a warning is logged without the value.

Account linking (server/account)

  • Matching a provider login to a person ignores deleted social ids.
  • When a verified e-mail belongs to a different person, that person's unverified e-mail social id is no longer confirmed and oidc_email_conflict is logged.
  • The ADMIN_EMAILS admin claim is only issued when the e-mail social id belongs to the person logging in.
  • The README notes that only identity providers whose email_verified claim is trustworthy should be used for linking; some providers let users edit their address and still report it as verified.

Callback

  • Relative redirect targets are resolved against the branding front URL.
  • The raw provider user object is no longer logged by handleProviderAuth.

Security considerations

  • An e-mail address is only used for linking when it is verified. The tenant-trust option is limited to an exact issuer and tenant match.
  • The group allowlist is opt-in and fails closed. Denial logs contain only the subject and the candidate e-mail, never group values or tokens.
  • Upstream already selected the provider identity before the e-mail, so the login never moved to another person. On a cross-person e-mail conflict, the other person's unverified e-mail social id is no longer confirmed, the conflict is logged, and the admin claim is issued only when the e-mail social id belongs to the person logging in. Deleted social ids are ignored.

Configuration

The variables are documented in pods/authProviders/README.md. Example:

OPENID_ALLOWED_GROUPS=huly-users,huly-admins
OPENID_SIGNUP_FOR_ALLOWED_GROUPS=true
OPENID_PROMPT=select_account

Tests

  • pods/authProviders: oidcClaims.test.ts covers case-insensitive string email_verified, # rejection, guest detection via #EXT# in preferred_username or upn, a foreign idp and acct=1, non-address email claims, and that email_verified/xms_edov do not verify a preferred_username fallback; oidcGroups.test.ts covers group access; openidCallback.test.ts covers redirects and the prompt; openid.test.ts covers denial before account creation, overage and missing claims, and signup gating.
  • server/account: utils.test.ts covers deleted social ids, e-mail conflicts and the admin claim on conflict.

loginOrSignUpWithProvider now
- ignores deleted social ids when matching a provider login to a person,
- leaves upstream's `targetSocialId ?? emailSocialId` person selection
  unchanged, so a login does not move to another person,
- when a verified email belongs to another person, does not confirm that
  person's unverified email social id and logs the OIDC email conflict,
- issues the ADMIN_EMAILS admin claim only when the email social id belongs to
  the person logging in.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…p allowlist and prompt

- Adopt the e-mail from the ID token when the provider marks it verified
  (email_verified, Entra xms_edov) or, with OPENID_TRUST_EMAIL=true, when
  issuer and tenant match OPENID_ISSUER; a verified userinfo e-mail stays a
  fallback.
- OPENID_ALLOWED_GROUPS: optional, fail-closed allowlist on the groups
  claim (missing claim or group overage denies). Denied users are sent to
  the login page with a localized message before any account is created.
- OPENID_SIGNUP_FOR_ALLOWED_GROUPS: allow OIDC signup for allowlisted users
  even when signup is disabled.
- OPENID_PROMPT: optional, validated OIDC prompt parameter.
- Callback redirects are made absolute against the branding front URL and
  raw provider user objects are no longer logged.
- Document the settings in pods/authProviders/README.md.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant