Skip to content

fix(authProviders): defensive split for IdPs that put full name in given_name#10919

Open
MichaelUray wants to merge 1 commit into
hcengineering:developfrom
MichaelUray:feat/openid-defensive-name-split
Open

fix(authProviders): defensive split for IdPs that put full name in given_name#10919
MichaelUray wants to merge 1 commit into
hcengineering:developfrom
MichaelUray:feat/openid-defensive-name-split

Conversation

@MichaelUray

@MichaelUray MichaelUray commented Jun 21, 2026

Copy link
Copy Markdown

What

pods/authProviders/src/openid.ts now uses a strict normalized heuristic to detect IdPs that emit given_name = full_name and family_name = "" (in violation of OIDC Core 1.0 §5.1). When that exact shape is detected, Huly splits name on whitespace instead of trusting given_name verbatim. Otherwise behaviour is unchanged.

Logic extracted into splitOidcName(claims) at pods/authProviders/src/oidcNameSplit.ts for testability.

Why

Authentik's default OIDC profile scope mapping ships with given_name = request.user.name (full display name) and no family_name (upstream issue tracking the conformance gap). For users like "Florian Preininger", the result was:

  • ctx.state.user.given_name = "Florian Preininger" → Huly stamped first_name = "Florian Preininger"
  • ctx.state.user.family_name = undefined → Huly's fallback name.split(' ').slice(1).join(' ') = "Preininger"
  • Account display: "Florian Preininger Preininger"

The same root cause has been reported against OnlyOffice as well (unanswered since 2024-12). Any RP that trusts given_name blindly and uses a split-fallback for family_name hits this.

Upstream resolution status (update 2026-06-22)

Authentik's maintainer (dewi-tik in #23231) has confirmed the root cause and will resolve it in v2026.8 via PR #21544 — adding separate First Name + Family Name user attributes, removing the need for any name-splitting heuristic at all. This is the correct call (splitting fails on non-western naming conventions per the names-falsehoods article).

This PR remains valuable as defense-in-depth:

  • Authentik installations on < 2026.8 (and admins who don't immediately reconfigure scope mappings after upgrade) are still affected.
  • Other OIDC providers with similar mis-configurations (Keycloak plugins, custom OPs) will produce the same shape and benefit from the same guard.

The heuristic is strict-shape-gated (only fires when given_name === full_name && family_name === ''), so a properly-configured Authentik 2026.8 + custom scope mapping makes this a no-op without needing the guard to be removed.

The heuristic

const isAuthentikDefaultShape =
  givenRaw !== '' &&
  givenRaw === fullName &&
  familyRaw === ''
  • Conformant IdPs (e.g. Google, GitHub, Microsoft): both given_name and family_name are populated correctly — heuristic does NOT trigger, behaviour unchanged.
  • Legitimate compound names (e.g. "Anna Lena Schmidt" with given_name: "Anna", family_name: "Lena Schmidt"): family_name is non-empty → heuristic does NOT trigger.
  • Authentik default + similar non-conformant IdPs: heuristic triggers → split on whitespace.

We explicitly avoided a looser "given_name contains family_name" heuristic because that would mangle legitimate compound names where family_name happens to be a substring of given_name.

Tests

pods/authProviders/src/oidcNameSplit.test.ts covers 10 cases:

  • Authentik-default shape → split applies
  • Conformant IdP → unchanged
  • Single-name user with Authentik shape → no degradation
  • Legitimate compound surname → unchanged
  • Compound first name → unchanged
  • Whitespace-padded input → trimmed
  • Multi-space input → collapsed
  • Missing name + fallback to username
  • Empty everything → {first: '', last: ''} (no crash)
  • Round-trip identity for already-split conformant input

Migration note

This change only affects how NEW logins populate first_name/last_name on the Huly account. Existing rows in global_account.person from before this fix retain the old values. Operators wanting to backfill can run a strict-heuristic UPDATE (happy to add a doc snippet if helpful).

Out-of-scope

  • Not fixing Authentik's default mapping itself (that's goauthentik/authentik#23231).
  • Not touching the SAML or other auth-provider strategies.
  • No new dependencies.

Checklist

  • Author + DCO sign-off on each commit
  • Unit tests cover the new heuristic
  • No behaviour change for conformant IdPs
  • No new dependencies
  • No breaking API change

… shape

Authentik's default 'profile' scope mapping has no first/last separation
and emits given_name = full display name. Huly's openid.ts previously
used given_name verbatim, then derived last_name from name.split(' '),
producing duplicated last names like "Florian Preininger Preininger".

Now uses a strict normalized heuristic: when given_name.trim() === name.trim()
AND family_name is missing/blank, fall back to splitting full-name on
whitespace. Otherwise use given_name/family_name as-is.

Per Codex Recommended (plan-review 2026-06-21): NO broad "given_name
contains family_name" fallback. That would mangle legitimate compound
names like "Anna Lena Schmidt".

Logic extracted into pure helper splitOidcName(claims) at
pods/authProviders/src/oidcNameSplit.ts for testability. 10 test cases
cover Authentik-default, conformant IdP, single-name, compound-surname,
whitespace, missing-name, multi-space, and empty-input.

Upstream-PR-candidate: this is a defensive fix that benefits any
non-Authentik IdP that also has the no-separation quirk.

Signed-off-by: Michael Uray <michael.uray@gmail.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
@huly-github-staging

Copy link
Copy Markdown

Connected to Huly®: UBERF-16523

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