Auto-provisioning users into correct organization based on email domain

My application uses React in the frontend. In the Auth0 side, I have a custom social connection named custom-oauth2 and 3 organizations: A, B, and C.

The desired objective is that upon the first login, based on the email domain, the user is provisioned into the corresponding organization. For example,

user@a.com => Organization A

jane@bcompany.io => Organization B

joe@ccorp.com => Organization C

The application is for B2B, so the login experience is for Business Users

How can I achieve this?

Thanks!

Hi @binh,

Welcome to the Auth0 Community!

When a user signs up for the first time, you can use a Post-Login Action that checks the user’s email domain (e.g., @bcompany.io ) and automatically adds them to the correct organization. This ensures a seamless onboarding experience for your business users, placing them in the right context from their very first login without any manual steps.

You could use this sample code snippet as a reference on how this could be implemented:

const { ManagementClient } = require('auth0');

exports.onExecutePostLogin = async (event, api) => {
  // 1. Run only on the user's first login.
  if (event.stats.logins_count !== 1) {
    return;
  }

  // 2. Define the mapping from email domain to organization ID directly.
  const orgMapping = {
    "a.com": "org_xxxxA", // Replace with Organization A's ID
    "bcompany.io": "org_xxxxB", // Replace with Organization B's ID
    "ccorp.com": "org_xxxxC"  // Replace with Organization C's ID
  };

  // 3. Get the user's email domain.
  const domain = event.user.email.split('@')[1];
  const orgId = orgMapping[domain];

  // If the domain doesn't match any organization, stop.
  if (!orgId) {
    console.log(`No organization mapping found for domain: ${domain}`);
    return;
  }

  // 4. Use the Management API to add the user to the organization.
  const managementApi = new ManagementClient({
    domain: event.secrets.DOMAIN,
    clientId: event.secrets.CLIENT_ID,
    clientSecret: event.secrets.CLIENT_SECRET,
  });

  try {
    await managementApi.organizations.addMembers(orgId, {
      members: [event.user.user_id]
    });
    console.log(`Successfully assigned user ${event.user.user_id} to organization ${orgId}`);
  } catch (err) {
    console.error(`Error assigning user to organization: ${err}`);
  }
};

I hope this helps and if you have further questions please let me know!
Best regards,
Remus

1 Like

Hi @remus.ivan ,

Thanks for the solution. The organization assignment works perfectly.

However, after the Post-Login Action runs and the user logs in to the application, they don’t have the organization context in the token. In other words, the application doesn’t know which organization they authenticate with.

How can I log the user in with the correct organization context after the organization assignment?

Hi @binh,

I am glad that this is working for you. You can achieve this by silently re-authenticating the user in your React app.

When your Post-Login Action successfully assigns a user to an organization, you should also add a final “flag” by setting a Custom Claim within the IdToken. This claim acts as a temporary signal, containing the ID of the organization the user was just assigned to. You can check out - Set ID Token Claims Using Actions, so in the try ... catch block you can add a line such as:

api.idToken.setCustomClaim("https://your-app.example.com/new_org_id", orgId);

The next step would be to create a dedicated route within your React application specifically to handle the post-login redirect, for example, a route at /callback . This route will render a component whose sole purpose is to inspect the results of the authentication before allowing the user to proceed into your main application ( check if the new flag was set ). You should configure this new URL (e.g., https://your-app.com/callback ) as the Allowed Callback URL in your Auth0 Application’s settings. This configuration forces Auth0 to send the user to this specific interception point in your app immediately after they authenticate.

Within this component, you can use the useAuth0() hook to get access to the user’s state. Then by using the useEffect hook, check to see if the custom claim is present in the token and if the flag is found, the component’s job is to trigger an immediate and silent re-authentication. You will do this by calling the loginWithRedirect function from the useAuth0 hook. Crucially, you must pass the organization ID from the custom claim to this function, for example: loginWithRedirect({ authorizationParams: { organization: newOrgId } }); .

Because the user has an active SSO session from the initial login, they will not be asked for their credentials again. They are instantly redirected back to your application, and this time, the new ID Token they receive will correctly contain the organization context (org_id ), completing the flow.

i hope this helps, and if you have further questions please let me know!
Kind regards,
Remus

1 Like

Hi @remus.ivan,

The custom claim solution works perfectly.
After the loginWithRedirect({....})call, the user logs in with the correct org context. However, the application loses appState, specifically its returnTo property, and it produces an unexpected effect, namely, the user lands on the wrong page after all the logins and redirections.

The flow is:
User enters the application at /desirable/path

After all the redirections, they actually land on the index page /

How can I retain the original appState and eventually redirect the user to the correct place?

Thanks!

Hi @binh,

I am glad this has worked well so far, but indeed there are a few other additions to be made.
You are right, the problem lies in the fact that the appState from the initial login is not automatically carried over to the second, silent loginWithRedirect call. Each loginWithRedirect is a new, isolated transaction.

The easiest and most reliable way to solve this is to manually persist the returnTo path across the redirects using the browser’s localStorage or sessionStorage.

First, you need to capture the appState from the first login attempt and save it before it gets lost. You can do this in a custom onRedirectCallback function provided to your Auth0Provider .

This function will intercept the appState and save its returnTo property to sessionStorage . It will also handle the final redirection at the end of the entire process.

// You could implement something similar to this

const onRedirectCallback = (appState) => {
  // Check if the appState from the login has a returnTo path.
  if (appState?.returnTo) {
    sessionStorage.setItem('auth_return_to', appState.returnTo);
  }
  navigate(sessionStorage.getItem('auth_return_to') || window.location.origin);
};

You then need to ensure your Auth0Provider uses this function by passing it as a property: onRedirectCallback={onRedirectCallback} .

You will also need to modify your /callback component to retrieve the path you just saved.
Inside the useEffect hook in your /callback component, retrieve the stored value:

const returnTo = sessionStorage.getItem('auth_return_to');

Then, inject this value back into the appState of the silent loginWithRedirect call. This explicitly tells the Auth0 SDK where the user should be sent after this second, final authentication is complete.

loginWithRedirect({
  appState: {
    // Re-apply the original intended path here
    returnTo: returnTo || '/'
  },
  authorizationParams: {
    organization: newOrgId,
  },
});

By following these steps, you create a chain that preserves the user’s original destination across both redirects, ensuring they land on the correct page after the seamless organization assignment is complete.

I hope this helps, so you can let me know how it goes and if you have other questions!
Thank you,
Remus

1 Like