Authentication in multitenant applications

The Surveys application uses the OpenID Connect (OIDC) protocol to authenticate users with Azure Active Directory (Azure AD).

Source: Original Postress-this.php?">Authentication in multitenant applications

The Surveys application uses ASP.NET Core, which has built-in middleware for OIDC. The following diagram shows what happens when the user signs in, at a high level.

Authentication flow
  1. The user clicks the “sign in” button in the app. This action is handled by an MVC controller.
  2. The MVC controller returns a ChallengeResult action.
  3. The middleware intercepts the ChallengeResult and creates a 302 response, which redirects the user to the Azure AD sign-in page.
  4. The user authenticates with Azure AD.
  5. Azure AD sends an ID token to the application.
  6. The middleware validates the ID token. At this point, the user is now authenticated inside the application.
  7. The middleware redirects the user back to application.

Register the app with Azure AD

To enable OpenID Connect, the SaaS provider registers the application inside their own Azure AD tenant.

To register the application, follow the steps in Quickstart: Register an application with the Microsoft identity platform.

To enable this functionality in the sample Surveys application, see the GitHub readme. Note the following:

  • For a multitenant application, you must configure the multitenanted option explicitly. This enables other organizations to access the application.
  • The reply URL is the URL where Azure AD will send OAuth 2.0 responses. When using the ASP.NET Core, this needs to match the path that you configure in the authentication middleware (see next section).

Configure the auth middleware

Note

This set of articles and the accompanying reference implementation include & use non-current tools, libraries, and technologies; but the concepts described are still relevant.

This section describes how to configure the authentication middleware in ASP.NET Core for multitenant authentication with OpenID Connect.

In your startup class, add the OpenID Connect middleware:

app.AddAuthentication().AddOpenIdConnect(options => {
    options.ClientId = configOptions.AzureAd.ClientId;
    options.ClientSecret = configOptions.AzureAd.ClientSecret; // for code flow
    options.Authority = Constants.AuthEndpointPrefix;
    options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
    options.PostLogoutRedirectUri = configOptions.AzureAd.PostLogoutRedirectUri;
    options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false };
    options.Events = new SurveyAuthenticationEvents(configOptions.AzureAd, loggerFactory);
});

Notice that some of the settings are taken from runtime configuration options. Here's what the middleware options mean:

  • ClientId. The application's client ID, which you got when you registered the application in Azure AD.
  • Authority. For a multitenant application, set this to https://login.microsoftonline.com/common/. This is the URL for the Azure AD common endpoint, which enables users from any Azure AD tenant to sign in. For more information about the common endpoint, see this blog post.
  • In TokenValidationParameters, set ValidateIssuer to false. That means the app will be responsible for validating the issuer value in the ID token. (The middleware still validates the token itself.) For more information about validating the issuer, see Issuer validation.
  • PostLogoutRedirectUri. Specify a URL to redirect users after the sign out. This should be a page that allows anonymous requests — typically the home page.
  • SignInScheme. Set this to CookieAuthenticationDefaults.AuthenticationScheme. This setting means that after the user is authenticated, the user claims are stored locally in a cookie. This cookie is how the user stays logged in during the browser session.
  • Events. Event callbacks; see Authentication events.

Also add the Cookie Authentication middleware to the pipeline. This middleware is responsible for writing the user claims to a cookie, and then reading the cookie during subsequent page loads.

app.AddAuthentication().AddCookie(options => {
    options.AutomaticAuthenticate = true;
    options.AutomaticChallenge = true;
    options.AccessDeniedPath = "/Home/Forbidden";
    options.CookieSecure = CookieSecurePolicy.Always;

    // The default setting for cookie expiration is 14 days. SlidingExpiration is set to true by default
    options.ExpireTimeSpan = TimeSpan.FromHours(1);
    options.SlidingExpiration = true;
});

Initiate the authentication flow

To start the authentication flow in ASP.NET MVC, return a ChallengeResult from the controller:

[AllowAnonymous]
public IActionResult SignIn()
{
    return new ChallengeResult(
        OpenIdConnectDefaults.AuthenticationScheme,
        new AuthenticationProperties
        {
            IsPersistent = true,
            RedirectUri = Url.Action("SignInCallback", "Account")
        });
}

This causes the middleware to return a 302 (Found) response that redirects to the authentication endpoint.

User login sessions

As mentioned, when the user first signs in, the Cookie Authentication middleware writes the user claims to a cookie. After that, HTTP requests are authenticated by reading the cookie.

By default, the cookie middleware writes a session cookie, which gets deleted once the user closes the browser. The next time the user next visits the site, they will have to sign in again. However, if you set IsPersistent to true in the ChallengeResult, the middleware writes a persistent cookie, so the user stays logged in after closing the browser. You can configure the cookie expiration; see Controlling cookie options. Persistent cookies are more convenient for the user, but may be inappropriate for some applications (say, a banking application) where you want the user to sign in every time.

About the OpenID Connect middleware

The OpenID Connect middleware in ASP.NET hides most of the protocol details. This section contains some notes about the implementation, that may be useful for understanding the protocol flow.

Bestseller No. 1
Mybaby Automatic Soap Dispenser, 1.1 Pound
  • Works With All Liquid Hand Soaps
  • Plays 20 Second Song That Teaches Children How To...
  • Motion Sensor Technology Provides Sanitary,...
  • Magnetically Attached Drip Tray Removes Easily For...
  • Self-cleaning Function Prevents Clogs And Mess
SaleBestseller No. 2
Asterom Walking Cane - Handmade Wolf Cane - Cool Walking Canes for Men and Women - Wooden, Carved, Unique - Walking Sticks for Men & Seniors (36 Inch)
  • STYLISH CANE FROM THE RED CARPET TO YOUR HOME! Our...
  • 100% SATISFACTION GUARANTEED! Buy with confidence,...
  • EXCEPTIONAL COMFORT DUE TO ERGONOMIC HANDLE. The...
  • CHOOSE THE CORRECT LENGTH TO AVOID SPINE PAIN....
  • WIDE QUAD CANE TIP COMPATIBLE. Need extra...

First, let's examine the authentication flow in terms of ASP.NET (ignoring the details of the OIDC protocol flow between the app and Azure AD). The following diagram shows the process.

Sign-in flow

In this diagram, there are two MVC controllers. The Account controller handles sign-in requests, and the Home controller serves up the home page.

Here is the authentication process:

  1. The user clicks the “Sign in” button, and the browser sends a GET request. For example: GET /Account/SignIn/.
  2. The account controller returns a ChallengeResult.
  3. The OIDC middleware returns an HTTP 302 response, redirecting to Azure AD.
  4. The browser sends the authentication request to Azure AD
  5. The user signs in to Azure AD, and Azure AD sends back an authentication response.
  6. The OIDC middleware creates a claims principal and passes it to the Cookie Authentication middleware.
  7. The cookie middleware serializes the claims principal and sets a cookie.
  8. The OIDC middleware redirects to the application's callback URL.
  9. The browser follows the redirect, sending the cookie in the request.
  10. The cookie middleware deserializes the cookie to a claims principal and sets HttpContext.User equal to the claims principal. The request is routed to an MVC controller.

Authentication ticket

If authentication succeeds, the OIDC middleware creates an authentication ticket, which contains a claims principal that holds the user's claims.

Note

Until the entire authentication flow is completed, HttpContext.User still holds an anonymous principal, not the authenticated user. The anonymous principal has an empty claims collection. After authentication completes and the app redirects, the cookie middleware deserializes the authentication cookie and sets HttpContext.User to a claims principal that represents the authenticated user.

Authentication events

During the authentication process, the OpenID Connect middleware raises a series of events:

  • RedirectToIdentityProvider. Called right before the middleware redirects to the authentication endpoint. You can use this event to modify the redirect URL; for example, to add request parameters. See Adding the admin consent prompt for an example.
  • AuthorizationCodeReceived. Called with the authorization code.
  • TokenResponseReceived. Called after the middleware gets an access token from the IDP, but before it is validated. Applies only to authorization code flow.
  • TokenValidated. Called after the middleware validates the ID token. At this point, the application has a set of validated claims about the user. You can use this event to perform additional validation on the claims, or to transform claims. See Working with claims.
  • UserInformationReceived. Called if the middleware gets the user profile from the user info endpoint. Applies only to authorization code flow, and only when GetClaimsFromUserInfoEndpoint = true in the middleware options.
  • TicketReceived. Called when authentication is completed. This is the last event, assuming that authentication succeeds. After this event is handled, the user is signed into the app.
  • AuthenticationFailed. Called if authentication fails. Use this event to handle authentication failures — for example, by redirecting to an error page.

To provide callbacks for these events, set the Events option on the middleware. There are two different ways to declare the event handlers: Inline with lambdas, or in a class that derives from OpenIdConnectEvents. The second approach is recommended if your event callbacks have any substantial logic, so they don't clutter your startup class. Our reference implementation uses this approach.

OpenID Connect endpoints

Azure AD supports OpenID Connect Discovery, wherein the identity provider (IDP) returns a JSON metadata document from a well-known endpoint. The metadata document contains information such as:

  • The URL of the authorization endpoint. This is where the app redirects to authenticate the user.
  • The URL of the “end session” endpoint, where the app goes to log out the user.
  • The URL to get the signing keys, which the client uses to validate the OIDC tokens that it gets from the IDP.
New
Rose Quartz Agate | Serving Tray with Brass Handles | Circular (Gold - Finish), Diameter(12 inch)
  • The natural look of agate stone creates a unique...
  • The edges of each piece are electroplated (NOT...
  • Handles are solid brass.
  • Available in 12", 14" and 16" diameter
  • Can do silver, rose gold, or gold plating for...
New
AANTHROPOLOGY By Rhea White Crystal Agate Cheese Platter/Tray | Circular (Gold - Finish), Diameter(12 inches)
  • The natural look of agate stone creates a unique...
  • The edges of each piece are electroplated (NOT...
  • Handles are solid brass.
  • Available in 12", 14" and 16" diameter
  • Can do silver, rose gold, or gold plating for...
New
Magenta Agate Aventurine Set of 4 Large Coasters/Sign Boards (Rose Gold - Finish)
  • Sold as a set of 4 pieces
  • 4" x 4"
  • 10 - 12 mm thick
  • Natural stone will vary in size, color, and...
  • Each piece has 4 rubber pads to avoid scratching...

By default, the OIDC middleware knows how to fetch this metadata. Set the Authority option in the middleware, and the middleware constructs the URL for the metadata. (You can override the metadata URL by setting the MetadataAddress option.)

OpenID Connect flows

By default, the OIDC middleware uses hybrid flow with form post response mode.

  • Hybrid flow means the client can get an ID token and an authorization code in the same round-trip to the authorization server.
  • Form post response mode means the authorization server uses an HTTP POST request to send the ID token and authorization code to the app. The values are form-urlencoded (content type = “application/x-www-form-urlencoded”).

When the OIDC middleware redirects to the authorization endpoint, the redirect URL includes all of the query string parameters needed by OIDC. For hybrid flow:

  • client_id. This value is set in the ClientId option.
  • scope = “openid profile”, which means it's an OIDC request and we want the user's profile.
  • response_type = “code id_token”. This specifies hybrid flow.
  • response_mode = “form_post”. This specifies form post response.

To specify a different flow, set the ResponseType property on the options. For example:

app.AddAuthentication().AddOpenIdConnect(options =>
{
    options.ResponseType = "code"; // Authorization code flow

    // Other options
}