0

I have an ASP.NET Core MVC app using the Microsoft Identity Platform for authentication, currently set up for multi-tenancy with one app registration. I want to extend this to support two separate app registrations (for two different tenants) and allow users from both tenants to authenticate, selecting the appropriate app registration dynamically based on the tenant ID.

How can I configure Program.cs to achieve this?

Current appsettings.json:

"AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "******.onmicrosoft.com",
    "TenantId": "**********",
    "ClientId": "**********",
    "CallbackPath": "/signin-oidc",
    "ClientSecret": "**********"
},
"AllowedTenants": [ "**********", "**********" ],
"DownstreamApi": {
    "BaseUrl": "https://graph.microsoft.com/v1.0",
    "Scopes": "User.Read"
}

Desired appsettings.json with two app registrations:

"AzureAdOne": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "******.onmicrosoft.com",
    "TenantId": "**********",
    "ClientId": "**********",
    "CallbackPath": "/signin-oidc",
    "ClientSecret": "**********"
},
"AzureAdTwo": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "******.onmicrosoft.com",
    "TenantId": "**********",
    "ClientId": "**********",
    "CallbackPath": "/signin-oidc",
    "ClientSecret": "**********"
},
"AllowedTenants": [ "**********", "**********" ],
"DownstreamApi": {
    "BaseUrl": "https://graph.microsoft.com/v1.0",
    "Scopes": "User.Read"
}

current Program.cs

string[] initialScopes = Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');

// Sign-in users with the Microsoft identity platform
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(options =>
    {
        Configuration.Bind("AzureAd", options);
        options.Events.OnTokenValidated = async context =>
        {
            string tenantId = context.SecurityToken.Claims.FirstOrDefault(x => x.Type == "tid" || x.Type == "http://schemas.microsoft.com/identity/claims/tenantid")?.Value;

            var allowedTenants = Configuration.GetSection("AllowedTenants").Get<string[]>().ToList();

            if (string.IsNullOrWhiteSpace(tenantId) || !allowedTenants.Contains(tenantId))
                throw new UnauthorizedAccessException("Unable to get tenantId from token or the tenant is not authorized.");
        };
    })
    .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
    .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
    .AddInMemoryTokenCaches();

In the login partial, users can select their company:

<a class="dropdown-item" href="/MicrosoftIdentity/Account/SignIn?scheme=TenantIdOne">Company One</a>
<a class="dropdown-item" href="/MicrosoftIdentity/Account/SignIn?scheme=TenantIdTwo">Company Two</a>

What’s the best way to implement this in Program.cs to dynamically select the app registration based on the tenant?

1
  • can you please elaborate what is your requirement and what you are trying to do ? Commented Oct 8, 2024 at 9:56

2 Answers 2

1

Based on this article, one can answer this question as follows:

To configure an ASP.NET Core MVC app to support authentication with two different app registrations, you can define multiple OpenID Connect schemes. Here’s how to set it up in your Program.cs, appsettings.json, and controller.

Step 1: Configure Program.cs

In the Program.cs, we set up two authentication schemes, appOne and appTwo, which correspond to two different app registrations (one for each tenant).

builder.Services.AddAuthentication(options =>
{
    // Set Cookie as the default scheme, which manages user sessions within the app.
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;

    // DefaultChallengeScheme indicates that we want to challenge users using OpenID Connect for login.
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie() // This handles user session management with cookies.
.AddOpenIdConnect("appOne", options =>
{
    // Bind configuration settings for the first app registration.
    builder.Configuration.GetSection("AppOne").Bind(options);
    options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;

    // Use Authorization Code flow, which is the standard flow for server-side apps.
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.SaveTokens = true;

    // Retrieves claims (user profile details) from the user info endpoint.
    options.GetClaimsFromUserInfoEndpoint = true;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        NameClaimType = "name" // Specifies how to extract the user's name from claims.
    };
    options.MapInboundClaims = false; // Ensures custom claims mapping.
})
.AddOpenIdConnect("appTwo", options => 
{
    // Bind configuration settings for the second app registration.
    builder.Configuration.GetSection("AppTwo").Bind(options);
    options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.SaveTokens = true;
    options.GetClaimsFromUserInfoEndpoint = true;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        NameClaimType = "name"
    };
});

Step 2: Define Configuration in appsettings.json

In the appsettings.json file, add separate sections for AppOne and AppTwo. This allows each OpenID Connect scheme to retrieve its specific settings, such as Authority, ClientId, ClientSecret, and callback paths.

{
  "AppOne": {
    "Authority": "https://login.microsoftonline.com/{tenantIdOne}",
    "ClientId": "your-client-id-for-app-one",
    "ClientSecret": "your-client-secret-for-app-one",
    "CallbackPath": "/signin-appOne",
    "SignedOutCallbackPath": "/signout-callback-appOne"
  },
  "AppTwo": {
    "Authority": "https://login.microsoftonline.com/{tenantIdTwo}",
    "ClientId": "your-client-id-for-app-two",
    "ClientSecret": "your-client-secret-for-app-two",
    "CallbackPath": "/signin-appTwo",
    "SignedOutCallbackPath": "/signout-callback-appTwo"
  }
}

Step 3: Create Login Methods in the Controller

In the controller, create two separate actions for each app. Each action initiates the authentication challenge for the specified scheme, either appOne or appTwo.

[AllowAnonymous]
[HttpGet]
public ActionResult LoginAppOne(string returnUrl)
{
    // Redirect to appOne's authentication challenge.
    return Challenge(new AuthenticationProperties
    {
        RedirectUri = !string.IsNullOrEmpty(returnUrl) ? returnUrl : "/", 
    }, "appOne");
}

[AllowAnonymous]
[HttpGet]
public ActionResult LoginAppTwo(string returnUrl)
{
    // Redirect to appTwo's authentication challenge.
    return Challenge(new AuthenticationProperties
    {
        RedirectUri = !string.IsNullOrEmpty(returnUrl) ? returnUrl : "/"
    }, "appTwo");
}

Step 4: Add Links in the View

To let users select the correct login option, add links in the view that call each login method:

<li class="nav-item">
    <a class="nav-link text-dark" href="~/api/Account/LoginAppOne">Login with App One</a>
</li>
<li class="nav-item">
    <a class="nav-link text-dark" href="~/api/Account/LoginAppTwo">Login with App Two</a>
</li>

How It Works:

  • Authentication Schemes: The two OpenID Connect schemes (appOne and appTwo) enable us to connect to different app registrations for each tenant.
  • Authentication Challenge: Challenge triggers the OpenID Connect flow for each scheme. The returnUrl parameter ensures users are redirected back to the original page after login.
  • User Session Management: By using AddCookie, the authentication session is managed with cookies, so users stay logged in until they sign out or the session expires.

This setup gives users the choice to log in through either app registration, dynamically adjusting based on their selection.

Sign up to request clarification or add additional context in comments.

Comments

0

I want to extend this to support two separate app registrations (for two different tenants) and allow users from both tenants to authenticate,

  • To allow users to authenticate from both tenants, you need to add the user from the other tenant as an external user in the default tenant.

  • When a user is added as a guest in another Azure AD tenant, they are invited to use that tenant’s resources. This lets them work with that tenant without creating a new account there.

  • I referred to this doc for adding the guest user.

  • Go to Azure Active directory - > All users - > new user -> Invite external user - > enter your details and hit review+invite .

enter image description here

enter image description here

Now I can successfully log in to my account in both Azure AD tenants.

enter image description here

enter image description here

enter image description here

6 Comments

Thank you for your response. I'm considering a different approach where Tenant A and Tenant B create their own app registrations independently. I would then add the ClientId and ClientSecret for each tenant in the appsettings.json. This way, users can select their tenant during the login process, and there's no need for Tenant A to manage the users of Tenant B.
If you set up separate app registrations for Tenant A and Tenant B, the app will use the first set of Azure AD credentials from your appsettings.json by default. This can cause problems because users won’t be able to log in unless they are added as guest users in each other's tenant.
For example, if a user from Tenant B tries to log into an app,they will get an error saying their account doesn’t exist in Tenant A. This is because the app defaults to using Tenant A for access. To log in, they need to be invited as a guest user in Tenant A.
Thank you for the response. That’s exactly what I want: when users select a tenant they don’t belong, they shouldn’t be able to login. The idea is that I want to pass the ClientId, so the app will decide which app registration it shall use. The idea is to build a custom login controller that will receive ClientId and based on this select the the app registration. What I currently have allows me to authenticate users based on their TenantId while having multi-tenant app registration. The only thing that I don’t like is that the app will send admin request to another tenant, which I don’t want.
I have posted an answer to this question
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.