LDAP connector loses MFA status of user on subsequent login
LDAP connector loses MFA status of user on subsequent login
Issue
MFA status of a FusionAuth user that is not migrated during a connector authentication is lost.
Steps to reproduce
Steps to reproduce the behavior:
- Set up a tenant and link with an LDAP connector.
- Set up an application in FusionAuth and link it with your web application.
- User initially logs in, user gets created.
- Configure user with MFA and/or application memberships (either through your web app workflow or manually via Fusion administrative pages)
- Logout
(Updated by @mooreds) .
Original issue
LDAP connector reconcile issues on subsequent logins
Description
Related to/Duplicate of #1220 and #1432 with an additional item. The use of the LDAP Connector reconcile lambda is causing the user to not just lose application memberships and not have groups be updated, but also causing a loss of their multi-factor authentication (e.g. their TOTP/Authenticator app link).
Affects versions
1.30.1
Steps to reproduce
Steps to reproduce the behavior:
- Set up a tenant and link with an LDAP connector.
- Set up an application in FusionAuth and link it with your web application.
- User initially logs in, user gets created.
- Configure user with MFA and/or application memberships (either through your web app workflow or manually via Fusion administrative pages)
- Logout
- Have the user log in again. The application membership and MFA information will no longer be available.
Expected behavior
- A reconcile lambda would allow the system to either create a new user or modify an existing user if the source data provider has relevant modifications. If there were a way for the lambda to lookup the existing user that could work. Or, if the lambda function was defined to have a parameter which was an existing user object matching the (tenant, email) tuple (or, null, if not found).
- As the multi-factor authentication is entirely handled within Fusion, there's no good way for that information to be pulled or managed from the LDAP source; the user wouldn't be given the TOTP QR code, for example.
- Once a user has configured multi-factor authentication, there should be no way to remove it without the user directly requesting it (or administrators being able to reset it after a review). With the LDAP Connector, the MFA will be removed at every login.
Platform
- OS: CentOS 7
- Database PostgresSQL 13
Additional context
Unlike in #1220, use of the OIDC IdP is not an option for us as we are not using Azure AD.
Our intended use case would be to allow our internal users to log in to the public applications using their Active Directory credentials for administrative actions. As this may need to happen from the public internet, any routing to internal federation would not be a desirable option. Also, the multi-factor authentication for a public login portal is not only our desired best practice but also a regulatory requirement.
I absolutely agree with the statement in the previous issues that "we didn't want to deal with merging data from another source of record, that can get really complicated." Trying to figure that out to "just work" and cover all use cases would be awful. A reconcile, however, would be a place to push those specific decisions off onto us as the deployers of the FusionAuth application. We could make the choices specific to our needs as to which data to keep and which is irrelevant, allowing us to do the work to reconcile changes.
If an LDAP IdP would be easier or fit your internal models, that would also work. The key issue currently is the loss of MFA when using an LDAP source.
Related
- https://github.com/FusionAuth/fusionauth-issues/issues/1821
Can you please share your LDAP lambda reconcile code? Please redact anything sensitive.
It's more or less the Active Directory sample with some additional logging.
// Using the response from an LDAP connector, reconcile the User. function reconcile(user, userAttributes) { user.email = userAttributes.mail; user.firstName = userAttributes.givenName; user.lastName = userAttributes.sn; user.active = true;
console.log("user.regs = " + user.registrations.length); console.log("user.twofactor = " + (user.twoFactor === null)); console.log("user.twofactorx = " + user.twoFactor); console.log("twofactor.methods = " + user.twoFactor.methods.length);
user.id = FusionAuth.ActiveDirectory.b64GuidToString(userAttributes['objectGUID;binary']);
// //3c219... is the default fusionauth application id for this install //this whole section I added in trying to see if I could do something to automate the addition of the registrations and roles.
//the problem is that the user.registrations is always an empty array, on every login. so if anyone is given a different // registration than is set up here, it functionally gets erased // if(user.registrations.length === 0) { if(JSON.stringify(userAttributes.distinguishedName).indexOf("something-in-the-DN") != -1) { user.registrations = [{ applicationId: "3c219...", roles: ['admin'] }];
} else { user.registrations = [{ applicationId: "3c219...", roles: ['user_manager'] }]; } }
So, on first login, I have to assign the user to the app (or admittedly I could add the application to the code above). The app then forces them to set up two-factor auth before doing anything else. Then, we log out. In the FusionAuth admin, you can see that the user is assigned my application and the Authenticator app is set up.
On the next log in, the Event Log for the "Lambda invocation result" is:
user.regs = 0 user.twofactor = false user.twofactorx = [object Object] twofactor.methods = 0
And the user has no registrations and no two-factor set up and has to go through the whole process again.
And just for kicks, trying to log, for example:
user.registrations[0].id
throws an invocation exception, as it "[c]annot read property "id" from undefined in
Thanks for the additional details. Just to confirm, you are not migrating users, but rather treating the LDAP service as the system of record?
Yes. Or, a system of record. My thought was: our internal users' passwords authenticating against our existing domain via LDAP, and our external (customer) users' passwords authenticating against the password database within Fusion (or, if requested, that customer's own accessible OIDC or SAML provider).
So this looks like this is partly a documentation issue. We set the registrations and groups based on the LDAP system, since it is the system of record (assuming you aren't migrating the user). So if you add a registration to the user using FusionAuth, next time authentication happens, we remove it because, well, FusionAuth is not the system of record :) . The same is true for group memberships. You need to make sure you set them on the user object based on the LDAP results. We will get the docs updated to make this clearer.
The MFA status being reset is a different situation and appears to be a bug.
OK, that sounds like something workable.
As far as the groups/registrations go, though, would there any way to expose a way to access information dynamically in the lambda? Needing to modify the lambda with new IDs and such every time there is a change to the list of applications and/or groups is an additional management need. If there were some way to hit the API to add more dynamic logic, it might hopefully make the management a little more straightforward.
You have two options:
- you could add information to the user in the LDAP directory as to which FusionAuth applications/groups the user should be in and include that in your LDAP requested attributes. Then, in the reconcile lambda, extract it and set the values in the FusionAuth user.
- you could wait for this issue to be delivered: https://github.com/FusionAuth/fusionauth-issues/issues/267 We don't have a firm timeline on that, though.
Is the specific issue with MFA being reset going to have to wait on #1445?
I'm not sure that #1445 will ever be implemented. Losing MFA status seems like a separate issue.
Sorry, looks like GH automatically closed this; it shouldn't have.
I'll re-open.
Haven't been able to recreate #1220 and #1432 yet.. it sounds like all of these may be related. I must be missing some detail yet. I'll investigate a bit further.
@bguyza is this the same issue? https://github.com/FusionAuth/fusionauth-issues/issues/1432
So we have recreated, and I can confirm the behavior that @jcpreston26 documented.
We are debating this one internally. Is it a bug, or working as designed.
The connector is the source of record for this user, so we could fix this and preserve the 2FA configuration, but I tend to think it is working as designed.
The connector is currently is charge of returning the source of record for the user until the user is migrated. This includes mostly everything about the user including registrations and group memberships.
If we were to preserve 2FA configuration, this has a few implications:
- You cannot mange 2FA for a user from a connector.
- You cannot migrate 2FA for a user from a connector.
- You cannot only manage 2FA for a user from FusionAuth, even when using a connector.
And if we were to manage it, then 2FA becomes an exception to the rule.
I tend to want to leave this as is - because it allows the most flexibility in the connector, and it affirms the connector to be the source of record.
There is some nuance here as well, since the 2FA configuration is not necessarily an attribute of the user, but a configuration for the user. I could probably convince myself of either solution.
Anyone have an opinion here? I suppose we could also add some additional configuration to the connector to let you decide, but this further complicates the configuration and the code.
FWIW (with a caveat that I'm coming at it from my perspective and there's likely other approaches): As a practical matter, I would assume that If I'm enabling 2FA in the Fusion application, it is because I'm intending to have Fusion manage the 2FA as the connector I'm using either can't or doesn't provide that functionality.
Thanks for the feedback @jcpreston26.
As a practical matter, I would assume that If I'm enabling 2FA in the Fusion application, it is because I'm intending to have Fusion manage the 2FA as the connector I'm using either can't or doesn't provide that functionality.
I think this is a valid perspective. It is likely common that the connector will not provide this function.
The tricky part is that we would either need to open up some additional config to allow you to configure this is your desired behavior, or we'd have to keep track of how owns the 2FA config in particular (FusionAuth or the current connector), and then have to think about what happens if both parties try to provide configuration. 🤮
I suppose if we were to choose one of those two options, I would vote to add additional connector or perhaps tenant configuration to indicate that 2FA is managed by the FusionAuth Tenant or the connector assigned to the user.
We'll discuss this one further.
@voidmain @bguyza @mooreds @robfusion any opinion?
The only issue I can see is how to distinguish between the lack of MFA support at the Connector and the Connector disabling MFA. We could store the MFA Connector separately from the authentication Connector and that might allow this to work as desired.
Internal notes:
Likely adding a policy will allow us to solve this use case. The policy would allow a connector to indicate it is managing 2FA for a user.
We could then optionally enforce this policy at the API level to fail 2FA modifications for a user that has 2FA managed by their connector policy.
Related but different: https://github.com/FusionAuth/fusionauth-issues/issues/1821