Link Saml provider to existing one - SAML providers are considered authoritative.
Operating System
14.2.1
Browser Version
Chrome 121.0.6167.85
Firebase SDK Version
10.8.0
Firebase SDK Product:
Auth
Describe your project's tooling
-
Web application, using VanillaJS and Django on Appengine on the backend.
-
Link accounts that use the same email is enable for the project.
Worth mentioning we proxy request as described here.
For testing purposed we have 2 different SAML identity Providers Auth0 and JumpCloud.
Describe the problem
SAML providers are treated as authoritative, while they probably shouldn't.
Specifically when a user account already exists (created either with user/password or Gooogle sign-in), and the same user tries to sign-in using the same email address through a SAMLProvider (single sign-on), if the sign-in is successful on the identity provider, the account is automatically linked with the existing account and auth/account-exists-with-different-credential is not raised.
This doesn't seem to reflect what's described in the documentation:
when a user tries to sign in to a provider (such as SAML) with an email that already exists for another provider (such as Google), the error auth/account-exists-with-different-credential is thrown (along with an AuthCredential object).
While I'm not an expert, this seems to be to be quite problematic from a security perspective.
Think about this scenario
Hypothesis
- IDP doesn't block authentication when email isn't verified
MyAppsupports email/password auth and signle sign-on (with a SAML conf)SingleSignOnAppis the identity provider and is configured to be a auth provider forMyApp.UserAis a legit userAttackeris trying to get hold of UserA
Steps
UserAcreates an account onMyAppwith email[email protected]andpasswordA.Attackercreates an account on theSingleSignOnAppusing[email protected]and apasswordB.Attackertries to log intoMyAppusing SSOAttackercan authenticate onSingleSignOnAppbecause they own the account- On
MyAppthe accounts are automatically linked and nowAttackercontrolsUserAaccount.
While this is true only if the IDP doesn't have email verification enabled, that can't be controlled and verified by the app. To me, it looks the same problem described for Social Providers, where they can't trusted so confirming the identity before linking is required.
Steps and code to reproduce issue
- Enable and set up a Saml provider authentication from the console.
- Create a user with
createUserWithEmailAndPassword[email: [email protected]] - Make sure user has verified their email
- [email protected] signs in using SSO with the same email address [email protected]
const samlProvider = new SAMLAuthProvider(<ssoProviderId>);
signInWithRedirect(auth, samlProvider);
...
const userCredential = await getRedirectResult(auth)
auth/account-exists-with-different-credentialis not raised and Providers are linked automatically
Hi @flea89, thank you for reporting this!
This is an expected behavior. This documentation needs to be updated.
When account linking is enabled for the project, the behavior is:
-
When user signs in with a trusted provider then signs in with a different trusted provider with the same email, both providers will be linked without errors. In your case, Email/Password with email verification (trusted) followed by SAML (trusted).
-
When user signs in with a trusted provider then signs in with untrusted provider with the same email (for example, Google followed by Facebook),
auth/account-exists-with-different-credentialwill be thrown.
IdPs that either own the domain or always require verification are considered trusted. SAML is considered as trusted. You can find the list of trusted providers at https://firebase.google.com/docs/auth/users#verified_email_addresses.
I will keep this bug open to track the doc update.
Hi @NhienLam, thanks a lot for your response. The way account linking works does make sense to me.
What I'm wondering is if trusting a SAML provider is correct decision in the first place.
Specifically I don't think we can consider this statement true for any IDP.
IdPs that either own the domain or always require verification are considered trusted
As an example I've created an app on Auth0 and enabled the SSO integration. I've successfully set it up as SAML provider for my Firebase application.
But by default Auth0 doesn't enforce email verification and emails with any domain can be added to the Auth0 app user list.
So, from what I can tell it's possible to successfully authenticate without having verified the email in the first place. That opens up to the problems described in the issue description, and makes SAML providers liable to the same security flaws social providers have if linking is automatic, doesn't it?
I reproduced the same problem by setting up JumpCloud as IDP.
Let me know if you need a demo or any more details.
ps: I don't have a lot of experience setting up an IDP and SSO integrations.
My setup is pretty basic, and I know it's possible to enforce a stricter security requirements.
At the same time I don't think firebase can "trust" that to be the case.
That's especially true in the context of a multi-tennant application where different IDPs are enabled and they're not necessarily controlled by the app developers.
Any updates on this one?
I'm worried this is quite a serious security hole 😇
Hi @flea89, I discussed with the team. Your concern is valid.
If you add a SAML provider which doesn't verify email address, can you try using the blocking function to mark the email as unverified - https://cloud.google.com/identity-platform/docs/blocking-functions#treating_certain_identity_provider_emails_as_verified ? Treating SAML provider emails as unverified should make SAML an untrusted provider. Hence, the behavior should now be:
When user signs in with a trusted provider then signs in with untrusted provider with the same email (for example, Google followed by Facebook), auth/account-exists-with-different-credential will be thrown.
@NhienLam thanks for getting back.
I reckon not trusting SAML providers should be the default thought. As you pointed out, the behaviour could still be overridden by https://cloud.google.com/identity-platform/docs/blocking-functions#treating_certain_identity_provider_emails_as_verified.
Do you have plans to fix it anytime soon?
Regarding your suggested workaround, it doesn't seem to work, by the time the beforeUserSignedIn the SamlProvider is already linked to the user and auth/account-exists-with-different-credential is not thrown.
const logger = require("firebase-functions/logger");
const {beforeUserSignedIn} = require("firebase-functions/v2/identity")
const gcipCloudFunctions = require("gcip-cloud-functions");
const authClient = new gcipCloudFunctions.Auth();
/**
* This function is exceuted before the user is signed in.
* It checks if the user is trying to sign in with a SAML provider and if the user is not yet
* linked to the SAML provider.
* It sets their email to false, so that the user is required to confirm their email address.
*
*
* @param {import("firebase-functions/v2/identity").AuthBlockingEvent} event
* @returns {Promise<import("firebase-functions/lib/common/providers/identity").BeforeSignInResponse|void>}
*/
async function beforeSignInCb (event) {
const providerId = event.additionalUserInfo?.providerId
logger.info(`ProviderId: ${providerId}`)
if(!providerId) {
logger.warn("No providerId found")
return;
}
const user = event.data;
const linkedProviders = user.providerData.map((p) => p.providerId)
logger.info(`Linked providers: ${linkedProviders.join(", ")}, set emailVerified to false`);
if(providerId.startsWith("saml")) {
logger.info(`Provider <${providerId}> is a SAML provider, setting emailVerified to false`)
return {
emailVerified: false,
}
}
}
exports.beforesignin = beforeUserSignedIn(beforeSignInCb)
Logs:
INFO ProviderId: saml.qy********
INFO Linked providers: google.com, saml.qy********
INFO Provider <saml.qy********> is a SAML provider, setting emailVerified to false
The exception is not thrown.
user.emailVerified appears to be false, as expected.
Obviously the provided snapshot is naive since it sets the emailVerified to always be false when signing in with SAML. In a real world scenario, after confirming the email, emailVerified would be True.
Am I missing something? Can you advise?
Good morning, any updates on this one?
Sorry for the late reply. I was out of office for the past weeks.
After discussing with our team, we currently don't have plans to change the current behavior and continue treating SAML providers as trusted. We recommend not using SAML providers that you don't trust.