0

I have an ASP.NET Core web app that is authenticating with Azure AD in a multi-tenant configuration using Microsoft.Identity.Web. We use a tenant/company identifier as the subdomain of our apps URL. (companyA.myapp.com, companyB.myapp.com). Some users have access to more than one tenant of the application, so we cannot map a Azure AD tenant directly to a single tenant/company in our app.

With Microsoft.Identity.Web, how is the state parameter set or manipulated as described here? I would like to follow the guidance provided here, but am not sure where to start. https://docs.microsoft.com/en-us/azure/active-directory/develop/reply-url#use-a-state-parameter

If you have several subdomains and your scenario requires that, upon successful authentication, you redirect users to the same page from which they started, using a state parameter might be helpful.

In this approach:

  1. Create a "shared" redirect URI per application to process the security tokens you receive from the authorization endpoint.
  2. Your application can send application-specific parameters (such as subdomain URL where the user originated or anything like branding information) in the state parameter. When using a state parameter, guard against CSRF protection as specified in section 10.12 of RFC 6749).
  3. The application-specific parameters will include all the information needed for the application to render the correct experience for the user, that is, construct the appropriate application state. The Azure AD authorization endpoint strips HTML from the state parameter so make sure you are not passing HTML content in this parameter.
  4. When Azure AD sends a response to the "shared" redirect URI, it will send the state parameter back to the application.
  5. The application can then use the value in the state parameter to determine which URL to further send the user to. Make sure you validate for CSRF protection.
4

1 回答 1

1

以下是我最终解决租户每个子域方案问题的 MS Login 无限重定向的方法。(试图为这个问题想出一个更好的名字。)

提供一个SigninRedirect控制器操作,该操作接受returnUrl我们必须验证以避免成为开放重定向的参数。

将 returnUrl 设置为的示例 URL companyA.example.com/foo&bar=1

https://signin.example.com/signin-redirect?returnUrl=companyA.example.com%2Ffoo%26bar%3D1
[Route("")]
public class SigninController : Controller
{
    private readonly IMediator _mediator;
    private readonly IConfiguration _configuration;
    public SigninController(IMediator mediator, IConfiguration configuration)
    {
        _mediator = mediator;
        _configuration = configuration;
    }
    [Authorize]
    [HttpGet("/signin-redirect")]
    public IActionResult SigninRedirect(string returnUrl)
    {
        string redirect;
        if (!string.IsNullOrEmpty(returnUrl) && IsValidSubdomainUrl(returnUrl))
        {
            redirect = returnUrl;
        }
        else
        {
            var appHost = _configuration.GetValue<string>("General:ApplicationHost");
            var home = new UriBuilder("https", appHost).Uri;
            return Redirect(home.AbsoluteUri);
        }
        return Redirect(redirect);
    }
    /// <summary>
    /// Avoid Open Redirect Vulnerability
    /// https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html
    /// </summary>
    /// <param name="returnUrl"></param>
    /// <returns></returns>
    private bool IsValidSubdomainUrl(string returnUrl)
    {
        var appHost = _configuration.GetValue<string>("General:ApplicationHost");
        var isValid = Uri.IsWellFormedUriString(returnUrl, UriKind.Absolute) &&
            Uri.TryCreate(returnUrl, UriKind.Absolute, out var uri) &&
            uri?.Host.EndsWith(appHost) == true;
        return isValid;
    }
}

在 ConfigureServices(IServiceCollection services) 中,将 cookie 设置为在所有子域之间共享,并配置一个OnRedirectToIdentityProvider事件以在用户尚未通过身份验证时重定向到您的登录 URL:


var cookieDomain = _configuration.GetValue<string>("General:CookieDomain");

services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromDays(3);
    options.Cookie.Domain = cookieDomain;
    options.Cookie.Path = "/";
    options.Cookie.SameSite = SameSiteMode.Lax;
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
});

services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(options =>
    {
        _configuration.Bind("AzureAd", options);
        options.Events ??= new OpenIdConnectEvents();
                        options.Events.OnRedirectToIdentityProvider += context => {
            var originalRequestUri = context.HttpContext.Request.GetUri();

            var signInHost = _configuration.GetValue<string>("General:SignInHost");
            var signInPath = _configuration.GetValue<string>("General:SignInUrl");

            if (!originalRequestUri.Host.Equals(signInHost, StringComparison.InvariantCultureIgnoreCase))
            {
                var signInUrl = QueryHelpers.AddQueryString(signInPath, "returnUrl", originalRequestUri.AbsoluteUri);

                // When on a subdomain and not authorized, then redirect to
                // our signin URL.
                context.Response.Redirect(signInUrl);

                // Let Microsoft.Identity.Web know that we already handled 
                // this redirect
                context.HandleResponse();
            }

            return Task.CompletedTask;
        };
    },
    cookieOptions => 
    {
        cookieOptions.Cookie.Domain = cookieDomain;
        cookieOptions.Cookie.Path = "/";
        cookieOptions.Cookie.SameSite = SameSiteMode.Lax;
    })
    .EnableTokenAcquisitionToCallDownstreamApi(new string[] { "user.read" })
    .AddDistributedTokenCaches();

services.ConfigureApplicationCookie(options =>
{
    options.Cookie.Domain = cookieDomain;
    options.Cookie.Name = ".AspNet.SharedCookie";
    options.Cookie.Path = "/";
    options.Cookie.SameSite = SameSiteMode.Lax;
});

配置定义:

"General:CookieDomain": ".example.com",
"General:ApplicationHost": "example.com",
"General:SignInHost": "signin.example.com",
"General:SignInUrl": "https://signin.example.com/signin-redirect"

此外,您将需要Microsoft Docs for Azure ADAzureAd中的常用配置部分。

"AzureAd:Instance": "https://login.microsoftonline.com/",
"AzureAd:Domain": "...",
"AzureAd:TenantId": "common",
"AzureAd:ClientId": "...",
"AzureAd:ClientSecret": "...",
"AzureAd:CallbackPath": "/signin-oidc",
"AzureAd:SignedOutCallbackPath": "/signout-callback-oidc",

子域每租户登录流序列图

于 2021-10-12T16:29:33.073 回答