Summary
A user cannot link a SAML identity to their account. The identity-link flow supports OAuth providers only, so someone who signs in with Entra SAML has no way to also attach their Google (or Microsoft OAuth) identity — or vice versa.
Acceptance Criteria
Notes
Current state:
auth/handlers_identity_link.go has no SAML awareness. StartIdentityLink resolves the provider through getProviderWithContext / providerConfig and builds an OAuth authorization URL via BuildIdentityLinkAuthorizationURL; HandleIdentityLinkCallback completes a confidential authorization-code exchange. None of that maps onto SAML, which is a POST-binding assertion flow with no code to redeem.
- The server already exposes SAML providers at
GET /saml/providers — on api.tmi.dev that returns entra-tmidev-saml ("Microsoft Entra ID (SAML)").
This is the blocker for the tmi-ux side: the identities tab (identities-tab.component.ts:205) populates its "link another account" list from AuthService.getAvailableProviders(), which fetches GET /oauth2/providers and therefore only ever lists OAuth providers. tmi-ux already has a getAvailableSAMLProviders() helper, so once a server flow exists the client change is small — but offering SAML in the picker before then would just produce failures.
Note that users.email is deliberately not unique (uniqueness is (provider, provider_user_id)), so the same person legitimately exists as separate rows per provider. Linking is the mechanism that reconciles them, which makes this gap more than cosmetic.
Reported from https://www.tmi.dev/: signed in with the Entra SAML account, wanted to link the Google account, and only OAuth providers were listed.
Summary
A user cannot link a SAML identity to their account. The identity-link flow supports OAuth providers only, so someone who signs in with Entra SAML has no way to also attach their Google (or Microsoft OAuth) identity — or vice versa.
Acceptance Criteria
POST /me/identities/link/startaccepts a SAML provider id (e.g.entra-tmidev-saml) and returns something the client can drive the round-trip withGET /me/identitiesand can be unlinked like any otherNotes
Current state:
auth/handlers_identity_link.gohas no SAML awareness.StartIdentityLinkresolves the provider throughgetProviderWithContext/providerConfigand builds an OAuth authorization URL viaBuildIdentityLinkAuthorizationURL;HandleIdentityLinkCallbackcompletes a confidential authorization-code exchange. None of that maps onto SAML, which is a POST-binding assertion flow with no code to redeem.GET /saml/providers— on api.tmi.dev that returnsentra-tmidev-saml("Microsoft Entra ID (SAML)").This is the blocker for the tmi-ux side: the identities tab (
identities-tab.component.ts:205) populates its "link another account" list fromAuthService.getAvailableProviders(), which fetchesGET /oauth2/providersand therefore only ever lists OAuth providers. tmi-ux already has agetAvailableSAMLProviders()helper, so once a server flow exists the client change is small — but offering SAML in the picker before then would just produce failures.Note that
users.emailis deliberately not unique (uniqueness is(provider, provider_user_id)), so the same person legitimately exists as separate rows per provider. Linking is the mechanism that reconciles them, which makes this gap more than cosmetic.Reported from https://www.tmi.dev/: signed in with the Entra SAML account, wanted to link the Google account, and only OAuth providers were listed.