Jonathan McPhail
Jonathan McPhail

Reputation: 68

Azure B2C deny="?" in asp.net MVC

I have an MVC application that I am trying to authenticate to Azure AD B2C. I want to use the deny="?" attribute in my web.config. All is well and good until I get redirected back to my application after auth, in which case asp.net isn't seeing that my user is authorized and I get sent back to the B2C login page, which results in an endless loop.

Startup.Auth.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

// The following using statements were added for this sample
using Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using System.Threading.Tasks;
using Microsoft.Owin.Security.Notifications;
using Microsoft.IdentityModel.Protocols;
using System.Web.Mvc;
using System.Configuration;
using System.IdentityModel.Tokens;
using WebApp_OpenIDConnect_DotNet_B2C.Policies;
using System.Threading;
using System.Globalization;
using Microsoft.Owin;

namespace WebApp_OpenIDConnect_DotNet_B2C
{
    public partial class Startup
    {
        // The ACR claim is used to indicate which policy was executed
        public const string AcrClaimType = "http://schemas.microsoft.com/claims/authnclassreference";
        public const string PolicyKey = "b2cpolicy";
        public const string OIDCMetadataSuffix = "/.well-known/openid-configuration";

        // App config settings
        private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
        private static string aadInstance = ConfigurationManager.AppSettings["ida:AadInstance"];
        private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
        private static string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];

        // B2C policy identifiers
        public static string SignUpPolicyId = ConfigurationManager.AppSettings["ida:SignUpPolicyId"];
        public static string SignInPolicyId = ConfigurationManager.AppSettings["ida:SignInPolicyId"];
        public static string ProfilePolicyId = ConfigurationManager.AppSettings["ida:UserProfilePolicyId"];

        public void ConfigureAuth(IAppBuilder app)
        {
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
            System.Web.Mvc.UrlHelper url = new System.Web.Mvc.UrlHelper(HttpContext.Current.Request.RequestContext);

            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                LoginPath = new PathString(url.Action("SignIn", "Account"))
            });

            OpenIdConnectAuthenticationOptions options = new OpenIdConnectAuthenticationOptions
            {
                // These are standard OpenID Connect parameters, with values pulled from web.config
                ClientId = clientId,
                RedirectUri = redirectUri,
                PostLogoutRedirectUri = redirectUri,
                Notifications = new OpenIdConnectAuthenticationNotifications
                { 
                    AuthenticationFailed = AuthenticationFailed,
                    RedirectToIdentityProvider = OnRedirectToIdentityProvider,
                },
                Scope = "openid",
                ResponseType = "id_token",

                // The PolicyConfigurationManager takes care of getting the correct Azure AD authentication
                // endpoints from the OpenID Connect metadata endpoint.  It is included in the PolicyAuthHelpers folder.
                ConfigurationManager = new PolicyConfigurationManager(
                    String.Format(CultureInfo.InvariantCulture, aadInstance, tenant, "/v2.0", OIDCMetadataSuffix),
                    new string[] { SignUpPolicyId, SignInPolicyId, ProfilePolicyId }),

                // This piece is optional - it is used for displaying the user's name in the navigation bar.
                TokenValidationParameters = new TokenValidationParameters
                {  
                    NameClaimType = "name",
                },
            };

            app.UseOpenIdConnectAuthentication(options);

        }

        // This notification can be used to manipulate the OIDC request before it is sent.  Here we use it to send the correct policy.
        private async Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
        {
            PolicyConfigurationManager mgr = notification.Options.ConfigurationManager as PolicyConfigurationManager;
            if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
            {
                OpenIdConnectConfiguration config = await mgr.GetConfigurationByPolicyAsync(CancellationToken.None, notification.OwinContext.Authentication.AuthenticationResponseRevoke.Properties.Dictionary[Startup.PolicyKey]);
                notification.ProtocolMessage.IssuerAddress = config.EndSessionEndpoint;
            }
            else
            {
                OpenIdConnectConfiguration config = await mgr.GetConfigurationByPolicyAsync(CancellationToken.None, notification.OwinContext.Authentication.AuthenticationResponseChallenge.Properties.Dictionary[Startup.PolicyKey]);
                notification.ProtocolMessage.IssuerAddress = config.AuthorizationEndpoint;
            }
        }

        // Used for avoiding yellow-screen-of-death
        private Task AuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
        {
            notification.HandleResponse();
            notification.Response.Redirect("/Home/Error?message=" + notification.Exception.Message);
            return Task.FromResult(0);
        }
    }
}

web.config

  <?xml version="1.0" encoding="utf-8"?>
  <!--
    For more information on how to configure your ASP.NET application, please visit
    http://go.microsoft.com/fwlink/?LinkId=301880
    -->
  <configuration>
    <appSettings>
      <add key="webpages:Version" value="3.0.0.0" />
      <add key="webpages:Enabled" value="false" />
      <add key="ClientValidationEnabled" value="true" />
      <add key="UnobtrusiveJavaScriptEnabled" value="true" />
      <add key="ida:Tenant" value="xxxx.onmicrosoft.com" />
      <add key="ida:ClientId" value="Myguid" />
      <add key="ida:AadInstance" value="https://login.microsoftonline.com/{0}{1}{2}" />
      <add key="ida:RedirectUri" value="https://localhost:44316/" />
      <add key="ida:SignUpPolicyId" value="B2C_1_JHA.SignUp" />
      <add key="ida:SignInPolicyId" value="B2C_1_JHA.SignIn" />
      <add key="ida:UserProfilePolicyId" value="B2C_1_JHA.Profile" />
    </appSettings>
    <location path="account/signin">
      <system.web>
        <authorization>
          <allow users="*" />
        </authorization>
      </system.web>
    </location>
    <system.web>
      <authorization>
        <deny users="?" />
      </authorization>
      <compilation debug="true" targetFramework="4.5" />
      <httpRuntime targetFramework="4.5" />
    </system.web>
    <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
        <dependentAssembly>
          <assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" />
          <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
        </dependentAssembly>
        <dependentAssembly>
          <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
          <bindingRedirect oldVersion="0.0.0.0-1.6.5135.21930" newVersion="1.6.5135.21930" />
        </dependentAssembly>
        <dependentAssembly>
          <assemblyIdentity name="System.IdentityModel.Tokens.Jwt" publicKeyToken="31bf3856ad364e35" culture="neutral" />
          <bindingRedirect oldVersion="0.0.0.0-4.0.20622.1351" newVersion="4.0.20622.1351" />
        </dependentAssembly>
        <dependentAssembly>
          <assemblyIdentity name="Microsoft.IdentityModel.Protocol.Extensions" publicKeyToken="31bf3856ad364e35" culture="neutral" />
          <bindingRedirect oldVersion="0.0.0.0-1.0.2.33" newVersion="1.0.2.33" />
        </dependentAssembly>
        <dependentAssembly>
          <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
          <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
        </dependentAssembly>
        <dependentAssembly>
          <assemblyIdentity name="Antlr3.Runtime" publicKeyToken="eb42632606e9261f" culture="neutral" />
          <bindingRedirect oldVersion="0.0.0.0-3.5.0.2" newVersion="3.5.0.2" />
        </dependentAssembly>
        <dependentAssembly>
          <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
          <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
        </dependentAssembly>
        <dependentAssembly>
          <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
          <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
        </dependentAssembly>
        <dependentAssembly>
          <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
          <bindingRedirect oldVersion="1.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
        </dependentAssembly>
        <dependentAssembly>
          <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
          <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
        </dependentAssembly>
        <dependentAssembly>
          <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
          <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
        </dependentAssembly>
        <dependentAssembly>
          <assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
          <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
        </dependentAssembly>
      </assemblyBinding>
    </runtime>
  </configuration>

Upvotes: 1

Views: 506

Answers (1)

Jack Zeng
Jack Zeng

Reputation: 2267

Reason for this issue:

For OpenIDConnect of DotNet, the authentication and the redirection are done at the same time. At your case, the authentication is always slower than the redirection. And, the redirect URI in your app is "https://localhost:44316/", which require authentication again because Request.IsAuthenticated is not yet set to be true. The new authentication will override the old authentication; hence it just keeps looping again and again.

The solution is simple. Change your redirect URI to a page that does not need authentication. For example:

<configuration>
<appSettings>
  <add key="webpages:Version" value="3.0.0.0" />
  <add key="webpages:Enabled" value="false" />
  <add key="ClientValidationEnabled" value="true" />
  <add key="UnobtrusiveJavaScriptEnabled" value="true" />
  <add key="ida:Tenant" value="xxxx.onmicrosoft.com" />
  <add key="ida:ClientId" value="Myguid" />
  <add key="ida:AadInstance" value="https://login.microsoftonline.com/{0}{1}{2}" />
  <add key="ida:RedirectUri" value="https://localhost:44316/Home/loginsuccess" />
  <add key="ida:SignUpPolicyId" value="B2C_1_JHA.SignUp" />
  <add key="ida:SignInPolicyId" value="B2C_1_JHA.SignIn" />
  <add key="ida:UserProfilePolicyId" value="B2C_1_JHA.Profile" />
</appSettings>
<location path="account/signin">
  <system.web>
    <authorization>
      <allow users="*" />
    </authorization>
  </system.web>
</location>
<location path="Home/loginsuccess">
  <system.web>
    <authorization>
      <allow users="*" />
    </authorization>
  </system.web>
</location>
<system.web>
  <authorization>
    <deny users="?" />
  </authorization>
  <compilation debug="true" targetFramework="4.5" />
  <httpRuntime targetFramework="4.5" />
</system.web>

...

Note: Here, https://localhost:44316/Home/loginsuccess should be a page that shows the user about their authentication status. By the way, you should add this redirect URI to your AD application in the new portal.

Upvotes: 1

Related Questions