FusionAuth
    • Home
    • Categories
    • Recent
    • Popular
    • Pricing
    • Contact us
    • Docs
    • Login

    MVC Application that routes to different (fusion auth) tenants

    Scheduled Pinned Locked Moved
    Q&A
    3
    4
    1.5k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • E
      ethalacker
      last edited by

      I am using a MVC app (.net Core and .net Framework MVC specifically) that needs to route to different tenants depending on the url they go to.

      For example:
      User A goes to www.mysite.com and I want them to go to Tenant1's login page.
      User B goes to www.clientname.mysite.com and I want them to go to Tenant2's login page.
      Similary,
      User C goes to www.anotherclient.mysite.com and I want them to go to Tenant3's login page.

      The issue I am running into, is that my OpenIdConnect Options is defined at the build and not per request.
      My OpenIdConnect Options look like the following:

      private const string clientSecret = "mySecretKey";
      private const string clientId = "myCleintIdGuid";
              
      public static OpenIdConnectAuthenticationOptions MakeOptions()
      {
          return new OpenIdConnectAuthenticationOptions {
              AuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType,
              SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
              ResponseType = OpenIdConnectResponseType.Code,
      
              CallbackPath = new PathString("/oauth/callback"),
      
              // this part needs to become dynamic
              RedirectUri = "https://clientname.mysite.com/oauth/callback",
      
              Scope = OpenIdConnectScope.OpenId,
              RequireHttpsMetadata = false,
      
              ClientId = clientId,
              Authority = "http://localhost:9011",
              ClientSecret = FusionAuthHelper.clientSecret,
      
              SaveTokens = true,
              RedeemCode = true,
      
              Notifications = new OpenIdConnectAuthenticationNotifications {
                  RedirectToIdentityProvider = OnRedirectToIdentityProvider(),
                  SecurityTokenValidated = OnSecurityTokenValidated()                   
              }
          };
      }
      

      Now, this works great for User B going to www.clientname.mysite.com (from my example above), but doesn't work for User A or User C

      My Fusion Auth Looks like the following:
      cbe0dc83-c8d1-49c0-a201-4b84cf19cb5e-image.png
      f109e5d6-5185-434b-9812-d6c5699a643d-image.png

      There might be other (FusionAuth)Applications for each tenant. But that can be a separate problem to solve if need be.

      How can I do this? Or, at least, what can I look up to figure out how to best solve this issue?

      1 Reply Last reply Reply Quote 0
      • danD
        dan
        last edited by

        The issue I am running into, is that my OpenIdConnect Options is defined at the build and not per request.

        I'm not super familiar with this framework, but could you build those options at the request?

        This SO post discusses some options (I think?): https://stackoverflow.com/questions/50488987/dynamically-set-owin-redirect-uri

        --
        FusionAuth - Auth for devs, built by devs.
        https://fusionauth.io

        1 Reply Last reply Reply Quote 0
        • E
          ethalacker
          last edited by

          But but that doesn't have access to the request.. or at least not that I saw/could figure out.

          Instead I did something like this:

          Startup.cs

          // app is Owin.IAppBuilder
          app.Use<MyCustomAuthMiddleware>(app, FusionAuthHelper.MakeOptions());
          

          MyCustomAuthMiddleware

          public class MyCustomAuthMiddleware : OpenIdConnectAuthenticationMiddleware
          {
              public MyCustomAuthMiddleware(OwinMiddleware next, IAppBuilder app, OpenIdConnectAuthenticationOptions options) : base(next, app, options)
              {
              }
          
              public override Task Invoke(IOwinContext context)
              {
                  // can do something like this if you want a service of something
                  // var session = DependencyResolver.Current.GetService<ISession>();
          
                  if (context.Request.Host.Value.Contains("clientname"))
                  {
                      Options.ClientId = "clientNames_clientId";
                      Options.RedirectUri = "clientNames_RedirectUri";
                      Options.ClientSecret = "clientNames_ClientSecret";
                      Options.TokenValidationParameters.ValidAudience = "clientNames_clientId";
                      Options.ConfigurationManager = new DynamicConfigurationManager("clientNames_clientId", "clientNames_tenantId");
                  }
                  else
                  {
                      Options.ClientId = "mySite_clientId";
                      Options.RedirectUri = "mySite_redirectUri";
                      Options.ClientSecret = "mySite_clientSecret";
                      Options.TokenValidationParameters.ValidAudience = "mySite_clientId";
                      Options.ConfigurationManager = new DynamicConfigurationManager("mySite_clientId", "mySite_tenantId");
                  }
                 // could continue this pattern for "anotherClient"
          
                  return base.Invoke(context);
              }
          }
          

          FusionAuthHelper

          public static class FusionAuthHelper
          {
              public static OpenIdConnectAuthenticationOptions MakeOptions()
              {
                  return new OpenIdConnectAuthenticationOptions {
                      AuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType,
                      SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
                      ResponseType = OpenIdConnectResponseType.Code,
          
                      CallbackPath = new PathString("/oauth/callback"),
          
                      Scope = OpenIdConnectScope.OpenId,
                      RequireHttpsMetadata = false,
          
                      Authority = "http://localhost:9011",
          
                      SaveTokens = true,
                      RedeemCode = true,
                      Notifications = new OpenIdConnectAuthenticationNotifications {
                          SecurityTokenValidated = OnSecurityTokenValidated
                      }
                  };
              }
          
              private static Task OnSecurityTokenValidated(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
              {
                  var userId = new Guid(notification.AuthenticationTicket.Identity.GetUserId());
                  var userRepository = DependencyResolver.Current.GetService<IUserRepository>();
                  var user = userRepository.Query().Where(u => u.IsActive)
                          .Where(u => u.Guid == userId)
                          .Select(u => new
                          {
                            //stuff
                          })
                          .SingleOrDefault();
                  if (user == null)
                  {
                      //do soemthing if the user isn't found
                  }
                  else
                  {
                      notification.AuthenticationTicket.Identity.AddClaim(new Claim("MyCoolClaim", user.Soemthing.ToString()));
                  }
                  return Task.CompletedTask;
              }
          }
          

          DynamicConfigurationManager

          public class DynamicConfigurationManager : IConfigurationManager<OpenIdConnectConfiguration>
          {
              private string clientId;
              private string tenantId;
          
              public DynamicConfigurationManager(string clientId, string tenantId)
              {
                  this.clientId = clientId;
                  this.tenantId = tenantId;
              }
          
              public async Task<OpenIdConnectConfiguration> GetConfigurationAsync(CancellationToken cancel)
              {
                  var authority = "http://localhost:9011";
                  var stsDiscoveryEndpoint = string.Format("{0}/.well-known/openid-configuration/{1}", authority, this.tenantId);
                  ConfigurationManager<OpenIdConnectConfiguration> configManager
                      = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint
                      , new OpenIdConnectConfigurationRetriever()
                      , new HttpDocumentRetriever() { RequireHttps = false });
          
          
                  var config = await configManager.GetConfigurationAsync(cancel);
          
                  config.EndSessionEndpoint = config.EndSessionEndpoint + "?client_id=" + this.clientId;
                  return config;
              }
          
              public void RequestRefresh()
              {
          
              }
          }
          

          here are my usings in case you wonder what packages I was using

          using Microsoft.AspNet.Identity;
          using Microsoft.Extensions.DependencyInjection;
          using Microsoft.IdentityModel.Protocols;
          using Microsoft.IdentityModel.Protocols.OpenIdConnect;
          using Microsoft.Owin;
          using Microsoft.Owin.Security.Cookies;
          using Microsoft.Owin.Security.Notifications;
          using Microsoft.Owin.Security.OpenIdConnect;
          using Owin;
          using System;
          using System.Linq;
          using System.Security.Claims;
          using System.Threading;
          using System.Threading.Tasks;
          using System.Web;
          using System.Web.Mvc;
          

          I'm not sure if this will create 1, or 2 options instances in the middleware.. So that might be an issue. I haven't tested it enough. Might just need to put all the FusionAuthHelper options into the middleware class.

          Also, I think this will only work for MVC .Net Framework. Still need to come up with solution for .NET Core

          1 Reply Last reply Reply Quote 1
          • T
            travis.whidden
            last edited by

            Not sure if this helps, as we don't currently use different tenants at this point in time, but we do for sure enforce sending the tenant id to each call:

            When you setup the OpenIdConnectOptions ---

            private const string TenantIdParameterName = "tenantId";
            
            ...
            
            options.Events.OnRedirectToIdentityProvider = context =>
            {
                             /* Fusion auth has the option for multiple tenants - when multiple tenants enabled,
                               we have to ensure we hit the right one for user auth. */
                            context.ProtocolMessage.SetParameter(TenantIdParameterName, authSettings.TenantId.ToString());
            }
            
            options.Events.OnRedirectToIdentityProviderForSignOut = context =>
                        {
                            context.ProtocolMessage.ClientId = authSettings.ClientId.ToString();
                            context.ProtocolMessage.SetParameter(TenantIdParameterName, authSettings.TenantId.ToString());
                            return Task.CompletedTask;
                        };
            
            

            Not sure if that helps you - you will have to look at the current HttpContext to decide what you want to do.

            1 Reply Last reply Reply Quote 2
            • First post
              Last post