The name-claim fallback in the ID token parsing is kept for the login
hint and display, but account matching now only considers a value that
came from the email claim, so a token without one no longer produces a
false account mismatch.
login_hint is a suggestion the IdP may ignore: with a silent flow configured
(DisablePromptLogin or max_age=0) and a live IdP session for another account,
the login completes with that account's token. On a registered peer the
management server rejects it as a user mismatch, but on a fresh profile the
peer silently registers under the wrong account and the profile is then bound
to it — every later login follows the stored hint straight back.
After the token exchange, compare the ID token's email against the hint the
flow was sent with. On a mismatch, do not log in to management with the token;
run one more round asking the IdP to re-decide the account (prompt=login, via
ForceAccountPrompt — DisablePromptLogin still wins there). If the prompted
round also comes back different, proceed with a warning: the address may
legitimately have changed, and refusing forever would lock the user out of the
profile while the management server still rejects a token that does not own
the peer. A token or profile with no email to compare is not judged.
The retry differs per platform because of who opens the browser:
- CLI (netbird login foreground) and Android run the whole flow in one
process, so the mismatch retries automatically: the browser reopens with
the account prompt within the same login attempt.
- On desktop the login is split between the daemon and the GUI: Login hands
the authorize URL to the GUI, WaitSSOLogin blocks for the token, and only
the GUI can open a browser. A new URL cannot be handed out from inside
WaitSSOLogin (its response has no field for one, kept that way to avoid a
proto change), so the daemon arms forceAccountPrompt, fails the round with
"connect again to choose the account", and builds the next Login's flow
with the prompt — the user's next connect is the retry.
The flag and the flow annotations live in daemon memory only; SwitchProfile
drops them so the previous profile's hint cannot judge the next profile's
token. The device code flow has no prompt parameter (RFC 8628), so a prompted
round there runs as-is and a repeated mismatch is let through with the
warning rather than looping.