We’re migrating users from Cognito to Auth0 using a custom DB connection with import_mode: true (lazy / automatic migration). All non-SSO orgs share a single user-pool DB connection. The intent is fully B2B — every user must belong to an Organization, no individual accounts.
We have a pre-built map of email/domain → [org_id, ...]. When a user lands on /authorize with an organization parameter, everything works: the JIT login script validates their Cognito password, Auth0 creates the user, assign_membership_on_login: true attaches them, login completes.
The problem is the bare URL case (no organization param):
-
With organization_usage: "require": the “user must belong to an org” check fires before any Post Login Action. The Action never runs, so we can’t pre-attach memberships. Login fails with "client requires organization membership, but user does not belong to any organization".
-
We can’t use Pre User Registration because event.organization isn’t exposed there, and pre-importing the user kills the lazy password migration (login script no longer fires for imported users).
-
Switching to organization_usage: "allow" does work: the Post Login Action runs, looks up the email in the map, calls organizations.members.create(...), and the user is attached during login.
The catch: with "allow", the post-login org picker now offers “Continue with personal account” alongside the user’s orgs. We don’t want that — it’s a strict B2B app.
Question: Is there a way to either:
-
Run a Post Login Action before the org-membership check fires, or
-
Hide the “personal account” option while keeping organization_usage: "allow" (so the Post Login attach works), or
-
Some other native pattern for “lazy migrate from legacy DB + assign user to org by email domain on first login + no personal accounts”?
Thanks!
Hi @marianoc
Welcome to the Auth0 Community!
I will need some time to investigate and I will come back with an update later today!
Kind Regards,
Nik
1 Like
Hi again!
- Run a Post Login Action before the org-membership check fires, or
No. The authentication pipeline order is strictly fixed: Custom DB Login → Auth0 Internal Org Check → Post-Login Actions.
- Hide the “personal account” option while keeping
organization_usage: "allow" (so the Post Login attach works), or
Not via a simple toggle in the Dashboard. The button is hardcoded into the Auth0 Organization Picker when usage is set to "allow" .
However, you can achieve your goal of “lazy migration + assign to org + no personal accounts.” You have two paths forward:
-
The cleanest, most native way to solve this is to never send the user to a bare /authorize URL. Instead of relying on Auth0 to ask for the email and figure out the routing, move the initial email prompt to your application.
->The user lands on your application and enters their email address: yourapp.com/login .
->Your frontend calls a lightweight, unauthenticated endpoint on your backend: GET /api/org-lookup?email=user@domain.com . Your backend returns the org_id .
->Your application redirects the user to Auth0, explicitly appending the parameter: /authorize?organization=org_id...
->Because the organization parameter is present, you can safely set organization_usage: "require" . Auth0 knows the target org immediately, the Custom DB script fires, the user is migrated, assign_membership_on_login triggers, and the flow completes without ever showing an Org Picker or a Personal Account button.
-
If you cannot build a login screen on your application and must use the Auth0 Universal Login page as the starting point, you can use the Auth0 Actions Redirect feature to perform a silent SSO bounce.
→ Set organization_usage: "allow" .
→ The user logs in via the bare URL. Your Post-Login Action fires. Since they didn’t pass an org, event.organization is undefined.
->The Action looks up the mapping, uses the Management API to assign the user to the org_id , and then immediately interrupts the login flow using api.redirect.sendUserTo() :
exports.onExecutePostLogin = async (event, api) => {
if (event.organization) return;
const targetOrgId = await assignUserToOrg(event.user.email);
const bounceUrl = `https://yourapp.com/api/auth/bounce?org=${targetOrgId}`;
api.redirect.sendUserTo(bounceUrl);
};
->The user lands on yourapp.com/api/auth/bounce . That route does absolutely nothing except issue an immediate HTTP 302 Redirect back to Auth0:
/authorize?organization=org_id&prompt=none&...
2.->Because you appended prompt=none , Auth0 recognizes the user’s active session, securely drops them into the specific Organization context without prompting them for a password or showing the Org Picker, and returns the tokens to your app.
Hope this helps, if you have any other questions or issues, let me know!
Kind Regards,
Nik