Building a Multi-Tenant Login in ASP.NET Core with Microsoft Entra ID
A small walk-through of standing up a real multi-tenant login — where any company’s users can sign in — using ASP.NET Core and Microsoft…
Building a Multi-Tenant Login in ASP.NET Core with Microsoft Entra ID
A small walk-through of standing up a real multi-tenant login — where any company’s users can sign in — using ASP.NET Core and Microsoft Entra ID. No magic, just an app registration and a few lines of config.
I wanted to build the sign-in experience for a SaaS app the way real products do it: a customer’s company signs up, and their employees log in with the work accounts they already have.
The tool that makes this possible is Microsoft Entra ID (formerly Azure AD) — Microsoft’s cloud identity service. It’s the same system that backs Microsoft 365, so most companies already have a directory of users in it. Each company’s directory is called a tenant. If my app accepts sign-ins from any tenant, any company can use it without me managing a single user account — that’s “multi-tenant” in one line.
The plan: register one app, let users from any tenant sign in, and watch their identity land in my app. Here’s how it went.
Step 1 : Get the mental model right
You register your app once in your own tenant, and that single registration can be used by many other tenants. Think of the registration as a blueprint — when someone from another company signs in for the first time, Entra stamps out a local copy of it (a service principal) inside their directory. Your tenant owns the definition; every customer tenant gets its own runtime instance.
Two settings control this, and they have to agree on both ends:
· In the portal — the registration’s audience is set to Multiple Entra ID tenants (signInAudience: AzureADMultipleOrgs). That’s what permits service principals to be created in foreign tenants.
· In the app — the OIDC authority has to point at the multi-tenant endpoint, not your specific tenant. That’s the AzureAd block in appsettings.json:

Microsoft.Identity.Web stitches Instance + TenantId into the authority it hands to the OpenID Connect handler — here https://login.microsoftonline.com/organizations. That /organizations segment is the whole trick: instead of routing logins through your tenant’s login URL, it routes them through the shared endpoint that accepts work accounts from any organization. (Swap it for a tenant GUID and the same app instantly becomes single-tenant — no code change, just config.)
So the audience flag and the authority are the two halves of the same switch: one says “let other tenants instantiate me,” the other says “send their users to the door that allows it.” You never construct that authority by hand, though — in Step 3 a single AddMicrosoftIdentityWebApp(Configuration.GetSection(“AzureAd”)) call reads this exact block and wires the whole OpenID Connect handler from it.
Step 2 : Register the app
In the Entra portal I went to App registrations -> New registration. Three choices that matter: a name, the account type (Multiple Entra ID tenants), and a redirect URI. Because this is a server-side web app, I chose the Web platform and pointed it at https://localhost:7039/signin-oidc — the address Entra sends the user back to after they authenticate.

From the Overview I grabbed the Application (client) ID, then created a client secret under Certificates & secrets. The client ID is public and safe to commit; the secret is not — that goes into a git-ignored file, never the repo.

Step 3 : Wire it into ASP.NET Core
Two packages — Microsoft.Identity.Web and its UI — and a few lines in Program.cs:

The config section sets “TenantId”: “organizations” — the multi-tenant flag. I added a [Authorize] Dashboard page that simply dumps every claim, so I could actually see what Entra sends back.
Step 4 : The snag

First sign-in attempt blew up: AADSTS700054: response_type ‘id_token’ is not enabled for the application. The app was asking Entra to hand back a token directly in the browser (the old implicit flow), which I’d deliberately left disabled. The fix wasn’t to enable it — it was to use the better flow. One line:
options.ResponseType = OpenIdConnectResponseType.Code;
This switches to the authorization-code flow: the browser only gets a short-lived code, and my server quietly trades it for tokens using the client secret. More secure, and the error disappears.
Step 5 : See it work
Restart, open https://localhost:7039, click Sign in. Microsoft’s login page appears, I authenticate, accept the one-time consent prompt, and land back in the app — signed in. The server logs confirm the token’s signature, lifetime, and audience all validated.


That tid is the whole point — the thread I’ll pull next to isolate each company’s data and onboard new organizations. But the sign-in flow works: any company’s users can now authenticate and reach the app.
Code’s on GitHub.
Next up: making sure only onboarded tenants get in.
메타데이터
- post_id
- b00666201cb7
- slug
- building-a-multi-tenant-login-in-asp-net-core-with-microsoft-entra-id-b00666201cb7
- url
- https://medium.com/@sainitesh/building-a-multi-tenant-login-in-asp-net-core-with-microsoft-entra-id-b00666201cb7
- canonical_url
- https://medium.com/@sainitesh/building-a-multi-tenant-login-in-asp-net-core-with-microsoft-entra-id-b00666201cb7
- author_url
- https://medium.com/@sainitesh
- status
- ok
- fetched_at
- 2026-06-26 21:52:29