ASP.NET MVC Identity - Using Internal User Authentication and Azure AD Together

We already have an ASP.NET MVC web application running that uses internal users using token authentication. This is implemented in the standard way provided by the ASP.NET MVC pattern.

We now have a requirement to extend this authentication model and allow an external Azure AD user to log into the web application for the configured tenant. I figured out everything on the Azure AD side. Thanks to the microsoft github example here

Both individual account authentication and Azure AD now work independently. But it doesn't work together. When I paste both middleware together it gives a problem.

Here is 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 does not 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 uninstall the local users middleware to get AAD working.

What I mean by AAD is not working, I cannot navigate to any protected action that is protected by the [Authoroze] attribute. Its raising the SecurityTokenValidated event and I can get all the AAD claims and be able to test it against my custom tenant. But it's only when at the end I redirect to the root of my app, which is a protected action, that it goes back to my custom login page. It doesn't seem to internally sign the user and create the required authentication cookies.

I would appreciate any ideas on what I am not seeing here.

thank

+3


source to share


2 answers


To support both separate accounts and another account from the social data provider, you only need to add them using the OWIN component.

And to pull users out of Azure AD, we need to emit a cookie issued from the web app and Azure AD. First, I modified the class ApplicationUser

to add a custom claim to determine if users are signed in from Azure AD or separate accounts, like below.

public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        if((this.Logins as System.Collections.Generic.List<IdentityUserLogin>).Count>0)
            userIdentity.AddClaim(new Claim("idp", (this.Logins as System.Collections.Generic.List<IdentityUserLogin>)[0].LoginProvider));
        return userIdentity;
    }
}

      



We can then change the method LogOff

to support sign out from Azure AD to clear cookies from Azure AD:

// POST: /Account/LogOff
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{

    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);

    var idpClaim = ClaimsPrincipal.Current.Claims.FirstOrDefault(claim => { return claim.Type == "idp"; });
    if (idpClaim!=null)
        HttpContext.GetOwinContext().Authentication.SignOut(
            OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);

    return RedirectToAction("Index", "Home");
}

      

0


source


It looks like your OpenID Connect auth is not connecting to your cookie. It looks like you need to specify SignInAsAuthenticationType

in OpenIdConnectAuthenticationOptions

which matches AuthenticationType

in your type CookieAuthenticationOptions

or ExternalCookie

auth.



0


source







All Articles