ASP.NET MVC Identity - Using internal User and Azure AD Authentication together

Viewed 4115

We are already running an ASP.NET MVC web application which is using internal users via token authentication. This is implemented in the standard way the ASP.NET MVC template provides.

Now we have a requirement to extend this authentication model and allow external Azure AD users to sign into the web application for configured tenant. I have figured out everything on the Azure AD side. Thanks to Microsoft's Azure Samples example.

Now both individual account authentication and Azure AD are working well independently. But they're not working together. When I insert both middleware together its giving issue.

Here's my startup_auth.cs file:

public partial class Startup
{
    
    public void ConfigureAuth(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
        
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);


        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider
            {
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
            }
        });            
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);


        string ClientId = ConfigurationManager.AppSettings["ida:ClientID"];            
        string Authority = "https://login.microsoftonline.com/common/";

    app.UseOpenIdConnectAuthentication(
        new OpenIdConnectAuthenticationOptions
        {
            ClientId = ClientId,
            Authority = Authority,
            TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
            {                        
                ValidateIssuer = false,
            },
            Notifications = new OpenIdConnectAuthenticationNotifications()
            {
                RedirectToIdentityProvider = (context) =>
                {                            
                    string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;                         
                    context.ProtocolMessage.RedirectUri = appBaseUrl;
                    context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
                    return Task.FromResult(0);
                },                        
                SecurityTokenValidated = (context) =>
                {
                    // retriever caller data from the incoming principal
                    string issuer = context.AuthenticationTicket.Identity.FindFirst("iss").Value;
                    string UPN = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.Name).Value;
                    string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;

                    if (
                        // the caller comes from an admin-consented, recorded issuer
                        (db.Tenants.FirstOrDefault(a => ((a.IssValue == issuer) && (a.AdminConsented))) == null)
                        // the caller is recorded in the db of users who went through the individual onboardoing
                        && (db.Users.FirstOrDefault(b =>((b.UPN == UPN) && (b.TenantID == tenantID))) == null)
                        )
                        // the caller was neither from a trusted issuer or a registered user - throw to block the authentication flow
                        throw new SecurityTokenValidationException();                            
                    return Task.FromResult(0);
                },
                AuthenticationFailed = (context) =>
                {
                    context.OwinContext.Response.Redirect("/Home/Error?message=" + context.Exception.Message);
                    context.HandleResponse(); // Suppress the exception
                    return Task.FromResult(0);
                }
            }
        });

    }
}

This configuration works well for local user accounts but doesn't work for AAD. To enable AAD authentication, I need to configure the UseCookieAuthentication part as shown below, which will break my local user account authentication.

app.UseCookieAuthentication(new CookieAuthenticationOptions { });

Basically I need to remove the local users middleware to get AAD work.

What I mean by AAD not working is, I am not able to go to any secured action which is protected by the [Authorize] attribute. It's calling the SecurityTokenValidated event, and I am able to get all AAD claims and able to validate against my custom tenant. But when I redirect to root of my app (which is a secured action) at the end, it throws me back to my custom login page. Seems it's not internally signing in the user and not creating the necessary authentication cookies.

I would appreciate any ideas on what I could be missing here.

Thanks

2 Answers
Related