Azure AD Enterprise Connection requires a new connection per client organisation

My required features are:

  • Allow password login (email must be verified)
  • 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:

Hi @cfong

Welcome to the Auth0 Community!

Would setting the email verified to true by default using a PostLogin Action trigger be a viable option for your current use case?

exports.onExecutePostLogin = async (event, api) => {

  if (event.connection.name === 'Global-Azure-AD-Connection') {

    api.user.setEmailVerified(true);
  }
};

Let me know if this works for you!

Kind Regards,
Nik

Hey Nik,

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?

Thanks, Calvin Fong

Hi again!

You want to support both password login (with email verification) and Azure AD login (without email verification) for multiple client organizations, but creating one enterprise connection per client is expensive and not scalable.

You can use a single multi-tenant Azure AD enterprise connection with Home Realm Discovery and Tenant ID validation in a Post-Login Action. This eliminates the need to create separate connections per client while maintaining security through Azure's domain ownership verification and Auth0's tenant ID validation.

[Root Cause]

Your current approach (one connection per client) is not necessary because:

  1. Azure AD enforces domain ownership — A tenant cannot create users with another company's domain unless they have proven DNS ownership. This prevents cross-tenant user creation at the Azure level.
  2. Auth0 can validate tenant IDs — You can add an extra security layer by storing each organization's Azure Tenant ID in Auth0 and validating it during login.
  3. Email verification is not required for Azure AD — Users logging in via Azure AD are already verified by Azure, so email_verified can be bypassed for this connection.

Why Your Current Approach Is Expensive:

  • Creating one connection per client organization requires separate Azure AD app registrations and Auth0 connections
  • This scales linearly with the number of clients
  • Each connection consumes resources and increases management overhead

Recommended Solution: Single Multi-Tenant Azure AD Connection with Tenant ID Validation

Step 1: Create a Single Multi-Tenant Azure AD Application in Azure

  1. Go to Azure Portal → App Registrations → New Registration
  2. Name the app (e.g., "My SaaS Multi-Tenant App")
  3. Select "Accounts in any organizational directory (Any Azure AD directory - Multitenant)"
  4. Set Redirect URI to https://YOUR_AUTH0_DOMAIN/login/callback
  5. Create a Client Secret and save it
  6. Note the Application (Client) ID and Tenant ID — You'll need these for Auth0

Step 2: Create a Single Azure AD Enterprise Connection in Auth0

  1. Navigate to Auth0 Dashboard → Connections → Enterprise → Microsoft Azure AD
  2. Name the connection (e.g., "Azure AD Multi-Tenant")
  3. Paste the Client ID and Client Secret from Azure
  4. Set the Domain to your primary domain (or leave it empty for multi-tenant)
  5. Enable "Use Common Endpoint" (this allows any Azure AD tenant to authenticate)
  6. Set "Always set email_verified" to false (optional — Azure AD users are already verified by Azure)
  7. Save the connection

Step 3: Enable the Connection for Your Application

  1. Go to Applications → Your App → Connections
  2. Enable the Azure AD Multi-Tenant connection
  3. Disable the connection for any other applications that shouldn't use it

Step 4: Configure Home Realm Discovery (Optional but Recommended)

Home Realm Discovery automatically routes users to the correct Azure AD tenant based on their email domain:

  1. In the Azure AD connection, go to the Login Experience tab
  2. Add domains for each client organization (e.g., companyA.com, companyB.com)
  3. Save the configuration

Now when a user enters employee@companyA.com, Auth0 automatically routes them to Company A's Azure AD tenant.

Step 5: Store Azure Tenant IDs in Auth0 Organizations

For each client organization, store their Azure Tenant ID:

  1. Navigate to Organizations → Select Organization
  2. Go to Metadata (at the bottom)
  3. Add a key-value pair:
    • Key: azure_tenant_id
    • Value: <Company's Azure Tenant ID> (e.g., 12345678-1234-1234-1234-123456789012)
  4. Save

To find the Azure Tenant ID, ask your client or retrieve it from Azure Portal → Azure Active Directory → Properties → Tenant ID.

Step 6: Implement Tenant ID Validation in a Post-Login Action

Create a Post-Login Action to validate that the user's Azure Tenant ID matches the organization's expected Tenant ID:

  1. Navigate to Actions → Library → Create Action
  2. Name it "Validate Azure Tenant ID"
  3. Select "Login / Post-Login" as the trigger
  4. Add the following code:
exports.onExecutePostLogin = async (event, api) => {
  // Only validate if the user is logging in via Azure AD and within an organization
  if (event.organization && event.organization.metadata) {
    const allowedTenantId = event.organization.metadata.azure_tenant_id;

    if (allowedTenantId) {
      // Find the Azure AD identity in the user's identities
      const azureIdentity = event.user.identities.find(
        (id) => id.connection === "Azure AD Multi-Tenant" // Use your connection name
      );

      if (!azureIdentity) {
        console.log("Security Warning: User does not have an Azure AD identity.");
        api.access.deny("Access Denied: Azure AD authentication required.");
        return;
      }

      // Extract the tenant ID from the Azure AD profile data
      const incomingTenantId = azureIdentity.profileData.tid || azureIdentity.profileData.tenantid;

      if (!incomingTenantId) {
        console.log("Security Warning: Could not find tenant ID in user profile.");
        api.access.deny("Security check failed: Missing Tenant ID.");
        return;
      }

      // Validate that the incoming tenant ID matches the organization's expected tenant ID
      if (incomingTenantId !== allowedTenantId) {
        console.log(
          `Security Alert: Blocked login. Organization expects ${allowedTenantId}, user came from ${incomingTenantId}`
        );
        api.access.deny(
          "Access Denied: You are logging in from an unauthorized Azure AD tenant."
        );
        return;
      }

      console.log(`Tenant ID validation passed for organization ${event.organization.id}`);
    }
  }
};
  1. Deploy the Action
  2. Add to Flow: Go to Actions → Flows → Login and add this Action to the flow

Step 7: Configure Email Verification for Password Login

For users logging in with password (not Azure AD), enforce email verification:

  1. Create a Post-Login Action named "Enforce Email Verification for Password Users"
  2. Add the following code:
exports.onExecutePostLogin = async (event, api) => {
  // Only enforce email verification for database (password) connections
  if (event.connection === "Username-Password-Authentication") {
    if (!event.user.email_verified) {
      api.access.deny(
        "Your email address is not verified. Please check your email for the verification link."
      );
      return;
    }
  }
  
  // Azure AD users are already verified by Azure, so no check needed
};
  1. Deploy and add to the Login flow

Step 8: Test the Setup

  1. Test password login:
    • Create a test user in your database connection
    • Verify email is required before login succeeds
  2. Test Azure AD login:
    • Use an employee email from one of your client organizations
    • Verify they are routed to the correct Azure AD tenant
    • Verify the tenant ID validation passes
  3. Test cross-tenant security:
    • Try logging in with an email from a different Azure AD tenant
    • Verify the login is denied with "unauthorized Azure AD tenant" message

This approach is significantly more cost-effective and scalable than creating one connection per client, while maintaining strong security through Azure's domain ownership verification and Auth0's tenant ID validation.

Kind Regards,
Nik

Thanks Nik,

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?

  exports.onExecutePostLogin = async (event, api) => {
    if (event.connection.name !== 'Global-Azure-AD') return;

    const autoEmailVerifiedDomains = ['companya.com', 'companyb.com', 'companyc.com'];
    const domain = event.user.email?.split('@')[1]?.toLowerCase();

    if (domain && autoEmailVerifiedDomains.includes(domain)) {
      api.idToken.setCustomClaim('email_verified', true);
    }
  };

Hi @cfong

Glad to be helpful with the issue at hand.

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,
    });
}

Let me know if you have any other questions!

Kind Regards,
Nik

Thanks Nik,

I’d like email_verified on the user to be true (as you’ve suggested), but can’t seem to find the method.

Should I use the management API (following this video https://www.youtube.com/watch?v=yuOFwTKDMek), or is there a simpler alternative?

Thanks,

Calvin Fong

Hi again @cfong

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!

Kind Regards,
Nik

Hey @nik.baleca

No worries. As an update the Management API returns an error message (essentially saying the management api doesn’t support my use case)

```
00:57:21: Error auto-verifying SSO user: BadRequestError: BadRequestError Status code: 400 Body: { “statusCode”: 400, “error”: “Bad Request”, “message”: “Email verification is not supported for enterprise users”, “errorCode”: “operation_not_supported” }
```

Do you know of any other alternatives?

Thanks, Calvin Fong

Hi,

It appears that it was another complete oversight from my side :sweat_smile:

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

  exports.onExecutePostLogin = async (event, api) => {
    if (event.connection.name !== 'Global-Azure-AD') return;

    const autoEmailVerifiedDomains = ['companya.com', 'companyb.com', 'companyc.com'];
    const domain = event.user.email?.split('@')[1]?.toLowerCase();

    if (event.stats.logins_count === 1){
   
        api.user.setUserMetadata("needsVerification", false);
  
}

When you stated that normal users need to be verified, how do you enforce this verification? (ex: on signup, MFA on login)

Kind Regards,
Nik