Allow companies to login via Azure AD (email must NOT require verification)
My current setup:
For each client org, create an “Organisation”
For each client org, create a “Microsoft Azure AD Enterprise Connection” → set the domain to the client’s domain
Link each “Organisation” to the corresponding “Enterprise Connection”
With my current setup, users will enter their email → get allocated an “Organisation” based on their email domain → be presented with configured logins (password OR their specific company’s Azure AD).
This process requires creating 1 enterprise connection per client organisation which gets expensive very quickly. Is there an alternative method that fits my requirements within auth0?
I’ve tried multiple alternatives:
“common endpoint” prevents “email verified by default”
using just 1 Azure AD enterprise connection requires you to specify a domain (mycompany.com)
Thanks for the help. I’m happy with whatever solution works and have full control over the code.
I need to protect against the following scenario:
If allowedCompanies = [“companya”, “companyb”, “companyc”]
Then “maliciousCompany.com" can create steve.jobs@companyb.com and then the solution above will cause issues.
I think your proposed solution will likely work with adjustments but I’m unfamiliar with the API. Are there docs you can link, or can you provide a solution for this particular scenario?
I completely understand your concerns, however, as far as my knowledge goes, this should be prevented automatically by Azure’s Domain Ownership. Basically, a tenant cannot create users with another company’s domain unless they have proven ownership via DNS records. Since you are using an enterprise connection, they will be redirected to Azure and unless they are part of that specific organization, they will not be added to the organization or they will have a completely new one created.
However, if you are still looking to implement some extra verification during this login process, I would recommend asking for the Tenant ID of the organization that they are part of in order to check it during login.
For each organization, you will need to add this ID to the Organization’s metadata in order to be able to perform checks. This can be done from Organizations → Organization_Name → Metadata (at the bottom of the tab).
Once you have done this, you will need to perform the checks as so:
if (event.organization) {
const allowedTenantId = event.organization.metadata.azure_tenant_id;
if (allowedTenantId) {
const azureIdentity = event.user.identities.find(
(id) => id.connection === CONNECTION_NAME
);
const incomingTenantId = azureIdentity.profileData.tid || azureIdentity.profileData.tenantid;
if (!incomingTenantId) {
console.log("Security Warning: Could not find 'tid' in user profile.");
api.access.deny("Security check failed: Missing Tenant ID.");
return;
}
if (incomingTenantId !== allowedTenantId) {
console.log(`Security Alert: Blocked login. Org expects ${allowedTenantId}, User came from ${incomingTenantId}`);
return api.access.deny("Access Denied: You are logging in from an unauthorized Organization Tenant.");
}
}
Let me know what do you think of this proposed solution and if you have any other questions!
I’ve gone with your original solution. From what I understand, I was wrong about there being an angle of attack (I thought something with guest accounts could cause issues).
I’ve added my code below. I’m 90% sure the extra domain check is unnecessary (since I’m already using organizations), but it’s sensitive information so don’t want to take the 10% risk. Would it be possible for you to double check the code below and ensure I’m not making any obvious mistakes?
Regarding your action code, what I would suggest instead of having the company domains exposed inside the action and having them added manually each time a new organization is added to your application, it would be to dynamically add these company domains to your application metadata.
Since you are creating organizations for each of these companies (most probably through the use of the Management API), you can also add their domains do the app’s metadata which is exposed in the action as client.metadata. This way you can dynamically add, retrieve and use them inside the action instead of having them hardcoded.
Also, I can see that you are adding the email_verified attribute to the ID Token, which is a good thing depending on what you are using it for. However, you are not modifying the root attribute inside their profile. I would recommend doing it as I have shown you above ( api.user.setEmailVerified(true);) or you can use the PATCH /api/v2/users endpoint to set email_verified to true. For the 2nd option, you will need to create a ManagementClient instance as such:
import { ManagementClient } from "auth0";
async function main() {
const client = new ManagementClient({
token: "<TOKEN>",
});
await client.users.update({
emailVerified: true,
});
}
My mistake, the suggested the method is quite old and completely forgot it is not available in the API object of an Action. You will need to use the Management API to set it to true since it is a root attribute of the user’s profile, sorry about that again!
It appears that it was another complete oversight from my side
Since enterprise connection federate an external identity, their root attributes cannot be modified directly within Auth0 (such as email_verified), only if it is updated on the IdPs side first.
Alternatively, you can add user_metadata to their profile within Auth0 (something like needsVerification = true) and check for that attribute when enforcing MFA. That would change your code to