Skip to content

Commit 9650c47

Browse files
atergaclaude
andcommitted
fix(fe): label SSO credentials by discovery domain, not IdP issuer
An SSO-linked access method (e.g. via `dfinity.org`'s Okta) was being rendered as "Google account" with the Google logo because `findConfig(iss, metadata)` matched on issuer alone. When the underlying IdP happens to be Google, the credential's `iss` is the Google issuer and collides with the direct "Sign in with Google" entry in `openid_configs` — even though the `aud` (client_id) is completely different. Changes: - `findConfig(iss, aud, metadata)` now matches on both `iss` and `aud` (the OAuth client_id). `aud` is accepted as `string | undefined`; callers that haven't been extended to track `aud` yet (`LastUsedIdentity`-based paths — see dfinity#3795) keep their issuer-only behavior for now, which is correct for direct providers and mis-attributes SSO credentials at those legacy sites only. - `openIdName(iss, sub, aud, metadata)` now also consults a per-device localStorage map (`ssoDomainStorage.ts`) populated at SSO link time. If the credential was linked via SSO on this device, the label becomes the `discovery_domain` the user typed (e.g. "dfinity.org account"). - `openIdLogo(iss, aud, metadata)` returns `undefined` for credentials that don't match any direct provider — the access-methods UI uses the generic SSO key icon as a fallback in that case. - `SsoDiscoveryResult` grows a `domain` field; both sign-in and add-access-method flows persist the `(iss, sub, aud) → domain` mapping after a successful SSO link, so the next render shows the correct label. - `OpenIdItem` renders the `<SsoIcon>` when `openIdLogo` returns undefined, and falls back to the literal "SSO" word when no domain is stored (cross-device case). Cross-device labelling (credential linked on device A, viewed on device B) remains wrong until the `discovery_domain` is persisted on the backend credential — tracked in dfinity#3795. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b62b682 commit 9650c47

23 files changed

Lines changed: 645 additions & 190 deletions

File tree

src/frontend/src/lib/components/ui/IdentityAvatar.svelte

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
identity.authMethod.openid.metadata !== undefined
1818
? openIdLogo(
1919
identity.authMethod.openid.iss,
20+
// `aud` not currently tracked on `LastUsedIdentity`; see #3795.
21+
// Falls back to issuer-only matching — correct for direct
22+
// providers, imprecise for SSO until that's fixed.
23+
undefined,
2024
identity.authMethod.openid.metadata,
2125
)
2226
: undefined,

src/frontend/src/lib/components/ui/ManageIdentities.svelte

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
identity.authMethod.openid.metadata !== undefined
4545
? openIdName(
4646
identity.authMethod.openid.iss,
47+
identity.authMethod.openid.sub,
48+
// `aud` not tracked on `LastUsedIdentity`; see #3795.
49+
undefined,
4750
identity.authMethod.openid.metadata,
4851
)
4952
: undefined}

src/frontend/src/lib/flows/addAccessMethodFlow.svelte.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { DiscoverableDummyIdentity } from "$lib/utils/discoverableDummyIdentity"
1414
import { DiscoverablePasskeyIdentity } from "$lib/utils/discoverablePasskeyIdentity";
1515
import { passkeyAuthnMethodData } from "$lib/utils/authnMethodData";
1616
import type { SsoDiscoveryResult } from "$lib/utils/ssoDiscovery";
17+
import { rememberSsoDomainForCredential } from "$lib/utils/ssoDomainStorage";
1718

1819
export class AddAccessMethodFlow {
1920
#view = $state<"chooseMethod" | "addPasskey" | "signInWithSso">(
@@ -119,9 +120,15 @@ export class AddAccessMethodFlow {
119120
* identity. Reuses {@link linkOpenIdAccount} by synthesizing an
120121
* `OpenIdConfig` from the two-hop discovery result — the issuer and
121122
* client_id we got from discovery plus a default name.
123+
*
124+
* On success we also remember `(iss, sub, aud) → domain` in localStorage
125+
* so the access-methods UI can later label this credential by the SSO
126+
* domain the user typed instead of by the underlying IdP's issuer.
122127
*/
123-
linkSsoAccount = (result: SsoDiscoveryResult): Promise<OpenIdCredential> => {
124-
const { clientId, discovery } = result;
128+
linkSsoAccount = async (
129+
result: SsoDiscoveryResult,
130+
): Promise<OpenIdCredential> => {
131+
const { domain, clientId, discovery } = result;
125132
const syntheticConfig: OpenIdConfig = {
126133
auth_uri: discovery.authorization_endpoint,
127134
jwks_uri: "",
@@ -133,6 +140,11 @@ export class AddAccessMethodFlow {
133140
auth_scope: selectAuthScopes(discovery.scopes_supported),
134141
client_id: clientId,
135142
};
136-
return this.linkOpenIdAccount(syntheticConfig);
143+
const credential = await this.linkOpenIdAccount(syntheticConfig);
144+
rememberSsoDomainForCredential(
145+
{ iss: credential.iss, sub: credential.sub, aud: credential.aud },
146+
domain,
147+
);
148+
return credential;
137149
};
138150
}

src/frontend/src/lib/flows/authFlow.svelte.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
selectAuthScopes,
3535
} from "$lib/utils/openID";
3636
import type { SsoDiscoveryResult } from "$lib/utils/ssoDiscovery";
37+
import { rememberSsoDomainForCredential } from "$lib/utils/ssoDomainStorage";
3738
import { nanosToMillis } from "$lib/utils/time";
3839

3940
interface AuthFlowOptions {
@@ -107,7 +108,7 @@ export class AuthFlow {
107108
type: "signUp";
108109
}
109110
> => {
110-
const { clientId, discovery } = ssoResult;
111+
const { domain, clientId, discovery } = ssoResult;
111112

112113
// Build a synthetic OpenIdConfig from SSO discovery result
113114
const syntheticConfig: OpenIdConfig = {
@@ -122,7 +123,37 @@ export class AuthFlow {
122123
client_id: clientId,
123124
};
124125

125-
return await this.continueWithOpenId(syntheticConfig);
126+
// Pre-fetch the JWT so we can record the `(iss, sub, aud) → domain`
127+
// mapping before handing off to the OpenID flow. Passing the JWT into
128+
// `continueWithOpenId` avoids a second round-trip to the provider.
129+
// The SSO domain is what the user typed; without remembering it here,
130+
// `OpenIdItem` would later render this credential as e.g. "Google
131+
// account" when the underlying IdP happens to be Google.
132+
this.#systemOverlay = true;
133+
let jwt: string;
134+
try {
135+
jwt = await requestJWT(
136+
{
137+
clientId,
138+
authURL: discovery.authorization_endpoint,
139+
authScope: syntheticConfig.auth_scope.join(" "),
140+
},
141+
{
142+
nonce: get(sessionStore).nonce,
143+
mediation: "required",
144+
},
145+
);
146+
} catch (error) {
147+
this.#view = "chooseMethod";
148+
throw error;
149+
} finally {
150+
this.#systemOverlay = false;
151+
authenticationV2Funnel.trigger(AuthenticationV2Events.ContinueWithOpenID);
152+
}
153+
const { iss, sub, aud } = decodeJWT(jwt);
154+
rememberSsoDomainForCredential({ iss, sub, aud }, domain);
155+
156+
return await this.continueWithOpenId(syntheticConfig, jwt);
126157
};
127158

128159
continueWithExistingPasskey = async (): Promise<bigint> => {

src/frontend/src/lib/flows/authLastUsedFlow.svelte.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ export class AuthLastUsedFlow {
6060
const issuer = lastUsedIdentity.authMethod.openid.iss;
6161
const config = findConfig(
6262
issuer,
63+
// `aud` not tracked on `LastUsedIdentity`; see #3795. Falls back
64+
// to issuer-only matching.
65+
undefined,
6366
lastUsedIdentity.authMethod.openid.metadata ?? [],
6467
);
6568
if (config === undefined) {

src/frontend/src/lib/locales/de.po

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ msgstr "{0}-Benutzer"
2222
msgid "{application} has moved to the new Internet Identity"
2323
msgstr "{application} ist zur neuen Internet Identity umgezogen"
2424

25+
msgid "{domainInput} didn't serve /.well-known/ii-openid-configuration (HTTP {0}). The domain owner needs to publish it for II to sign you in."
26+
msgstr ""
27+
28+
msgid "{domainInput}'s /.well-known/ii-openid-configuration is malformed: {0}"
29+
msgstr ""
30+
31+
msgid "{domainInput}'s /.well-known/ii-openid-configuration is malformed."
32+
msgstr ""
33+
2534
msgid "{identityName} has been removed from this device."
2635
msgstr "{identityName} wurde von diesem Gerät entfernt."
2736

@@ -229,6 +238,9 @@ msgstr "Mit {name} fortfahren"
229238
msgid "Continue with passkey"
230239
msgstr "Mit Passkey fortfahren"
231240

241+
msgid "Couldn't reach {domainInput}. Check the spelling and your network, then try again."
242+
msgstr ""
243+
232244
msgid "Create account"
233245
msgstr "Konto erstellen"
234246

@@ -389,9 +401,6 @@ msgstr "Wie unterscheidet sich Internet Identity von anderen Web3-Authentifizier
389401
msgid "How to stay secure"
390402
msgstr "So bleiben Sie sicher"
391403

392-
msgid "HTTP {0}"
393-
msgstr ""
394-
395404
msgid "I Acknowledge"
396405
msgstr "Ich bestätige"
397406

@@ -491,9 +500,6 @@ msgstr "Ungültige Wiederherstellungsphrase"
491500
msgid "Invalid request"
492501
msgstr "Ungültige Anfrage"
493502

494-
msgid "invalid response"
495-
msgstr ""
496-
497503
msgid "Is my Face ID or Fingerprint stored in Internet Identity?"
498504
msgstr "Werden Face ID oder Fingerabdruck in Internet Identity gespeichert?"
499505

@@ -617,9 +623,6 @@ msgstr "Konto benennen"
617623
msgid "Name your identity"
618624
msgstr "Benennen Sie Ihre Identität"
619625

620-
msgid "network error"
621-
msgstr ""
622-
623626
msgid "No data is shared with Google, Microsoft or Apple on which applications you log in with Internet Identity."
624627
msgstr "Es werden keine Daten an Google, Microsoft oder Apple darüber weitergegeben, bei welchen Anwendungen Sie sich mit Internet Identity anmelden."
625628

@@ -827,6 +830,9 @@ msgstr "Sitzung abgelaufen"
827830
msgid "Set as default sign-in"
828831
msgstr "Als Standardanmeldung festlegen"
829832

833+
msgid "Several SSO sign-ins are in flight already. Wait a moment and try again."
834+
msgstr ""
835+
830836
msgid "Show all"
831837
msgstr "Alle anzeigen"
832838

@@ -894,7 +900,22 @@ msgstr ""
894900
msgid "Source code"
895901
msgstr "Quellcode"
896902

897-
msgid "SSO is not available for \"{domainInput}\" yet."
903+
msgid "SSO is not available for \"{domainInput}\" yet. Ask an II admin to register this domain."
904+
msgstr ""
905+
906+
msgid "SSO provider misconfigured: authorization endpoint is not HTTPS. ({msg})"
907+
msgstr ""
908+
909+
msgid "SSO provider misconfigured: authorization endpoint points to a different host than the issuer. ({msg})"
910+
msgstr ""
911+
912+
msgid "SSO provider misconfigured: issuer doesn't match the configured hostname. ({msg})"
913+
msgstr ""
914+
915+
msgid "SSO provider misconfigured: issuer URL is not HTTPS. ({msg})"
916+
msgstr ""
917+
918+
msgid "SSO provider's discovery document is malformed: {msg}"
898919
msgstr ""
899920

900921
msgid "SSO sign-in failed. Please try again."
@@ -981,9 +1002,6 @@ msgstr "Diese App ist zur neuen Internet Identity umgezogen"
9811002
msgid "This browser is not supported."
9821003
msgstr ""
9831004

984-
msgid "This domain isn't correctly configured for Internet Identity ({detail})."
985-
msgstr ""
986-
9871005
msgid "This identity has already been upgraded to the new experience."
9881006
msgstr "Diese Identität wurde bereits auf die neue Version aktualisiert."
9891007

@@ -1032,6 +1050,9 @@ msgstr "So fahren Sie fort:"
10321050
msgid "To finish setting up your passkey, follow the instructions shown on your <0>new device</0>."
10331051
msgstr "Um die Einrichtung Ihres Passkeys abzuschließen, folgen Sie den Anweisungen auf Ihrem <0>neuen Gerät</0>."
10341052

1053+
msgid "Too many recent attempts for {domainInput}. Wait a few minutes and try again."
1054+
msgstr ""
1055+
10351056
msgid "Type each word in the correct order:"
10361057
msgstr "Geben Sie jedes Wort in der richtigen Reihenfolge ein:"
10371058

src/frontend/src/lib/locales/en.po

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ msgstr "{0} user"
2222
msgid "{application} has moved to the new Internet Identity"
2323
msgstr "{application} has moved to the new Internet Identity"
2424

25+
msgid "{domainInput} didn't serve /.well-known/ii-openid-configuration (HTTP {0}). The domain owner needs to publish it for II to sign you in."
26+
msgstr "{domainInput} didn't serve /.well-known/ii-openid-configuration (HTTP {0}). The domain owner needs to publish it for II to sign you in."
27+
28+
msgid "{domainInput}'s /.well-known/ii-openid-configuration is malformed: {0}"
29+
msgstr "{domainInput}'s /.well-known/ii-openid-configuration is malformed: {0}"
30+
31+
msgid "{domainInput}'s /.well-known/ii-openid-configuration is malformed."
32+
msgstr "{domainInput}'s /.well-known/ii-openid-configuration is malformed."
33+
2534
msgid "{identityName} has been removed from this device."
2635
msgstr "{identityName} has been removed from this device."
2736

@@ -229,6 +238,9 @@ msgstr "Continue with {name}"
229238
msgid "Continue with passkey"
230239
msgstr "Continue with passkey"
231240

241+
msgid "Couldn't reach {domainInput}. Check the spelling and your network, then try again."
242+
msgstr "Couldn't reach {domainInput}. Check the spelling and your network, then try again."
243+
232244
msgid "Create account"
233245
msgstr "Create account"
234246

@@ -389,9 +401,6 @@ msgstr "How does Internet Identity compare to other Web3 authentication tools?"
389401
msgid "How to stay secure"
390402
msgstr "How to stay secure"
391403

392-
msgid "HTTP {0}"
393-
msgstr "HTTP {0}"
394-
395404
msgid "I Acknowledge"
396405
msgstr "I Acknowledge"
397406

@@ -491,9 +500,6 @@ msgstr "Invalid recovery phrase"
491500
msgid "Invalid request"
492501
msgstr "Invalid request"
493502

494-
msgid "invalid response"
495-
msgstr "invalid response"
496-
497503
msgid "Is my Face ID or Fingerprint stored in Internet Identity?"
498504
msgstr "Is my Face ID or Fingerprint stored in Internet Identity?"
499505

@@ -617,9 +623,6 @@ msgstr "Name account"
617623
msgid "Name your identity"
618624
msgstr "Name your identity"
619625

620-
msgid "network error"
621-
msgstr "network error"
622-
623626
msgid "No data is shared with Google, Microsoft or Apple on which applications you log in with Internet Identity."
624627
msgstr "No data is shared with Google, Microsoft or Apple on which applications you log in with Internet Identity."
625628

@@ -827,6 +830,9 @@ msgstr "Session timed out"
827830
msgid "Set as default sign-in"
828831
msgstr "Set as default sign-in"
829832

833+
msgid "Several SSO sign-ins are in flight already. Wait a moment and try again."
834+
msgstr "Several SSO sign-ins are in flight already. Wait a moment and try again."
835+
830836
msgid "Show all"
831837
msgstr "Show all"
832838

@@ -894,8 +900,23 @@ msgstr "Something went wrong while sharing attributes. Please try again; if the
894900
msgid "Source code"
895901
msgstr "Source code"
896902

897-
msgid "SSO is not available for \"{domainInput}\" yet."
898-
msgstr "SSO is not available for \"{domainInput}\" yet."
903+
msgid "SSO is not available for \"{domainInput}\" yet. Ask an II admin to register this domain."
904+
msgstr "SSO is not available for \"{domainInput}\" yet. Ask an II admin to register this domain."
905+
906+
msgid "SSO provider misconfigured: authorization endpoint is not HTTPS. ({msg})"
907+
msgstr "SSO provider misconfigured: authorization endpoint is not HTTPS. ({msg})"
908+
909+
msgid "SSO provider misconfigured: authorization endpoint points to a different host than the issuer. ({msg})"
910+
msgstr "SSO provider misconfigured: authorization endpoint points to a different host than the issuer. ({msg})"
911+
912+
msgid "SSO provider misconfigured: issuer doesn't match the configured hostname. ({msg})"
913+
msgstr "SSO provider misconfigured: issuer doesn't match the configured hostname. ({msg})"
914+
915+
msgid "SSO provider misconfigured: issuer URL is not HTTPS. ({msg})"
916+
msgstr "SSO provider misconfigured: issuer URL is not HTTPS. ({msg})"
917+
918+
msgid "SSO provider's discovery document is malformed: {msg}"
919+
msgstr "SSO provider's discovery document is malformed: {msg}"
899920

900921
msgid "SSO sign-in failed. Please try again."
901922
msgstr "SSO sign-in failed. Please try again."
@@ -981,9 +1002,6 @@ msgstr "This app has moved to the new Internet Identity"
9811002
msgid "This browser is not supported."
9821003
msgstr "This browser is not supported."
9831004

984-
msgid "This domain isn't correctly configured for Internet Identity ({detail})."
985-
msgstr "This domain isn't correctly configured for Internet Identity ({detail})."
986-
9871005
msgid "This identity has already been upgraded to the new experience."
9881006
msgstr "This identity has already been upgraded to the new experience."
9891007

@@ -1032,6 +1050,9 @@ msgstr "To continue:"
10321050
msgid "To finish setting up your passkey, follow the instructions shown on your <0>new device</0>."
10331051
msgstr "To finish setting up your passkey, follow the instructions shown on your <0>new device</0>."
10341052

1053+
msgid "Too many recent attempts for {domainInput}. Wait a few minutes and try again."
1054+
msgstr "Too many recent attempts for {domainInput}. Wait a few minutes and try again."
1055+
10351056
msgid "Type each word in the correct order:"
10361057
msgstr "Type each word in the correct order:"
10371058

0 commit comments

Comments
 (0)