Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 897
paresh.bijvani Avatar asked Jul 07 '17 21:07

paresh.bijvani


People also ask

How do I use Microsoft Identity Azure AD to authenticate your users?

Enable Azure Active Directory in your App Service app. Sign in to the Azure portal and navigate to your app. Select Authentication in the menu on the left. Click Add identity provider.

How would you implement Azure AD authentication in ASP NET application?

In the New ASP.NET Project dialog, select MVC, and then click Change Authentication. On the Change Authentication dialog, select Organizational Accounts. These options can be used to automatically register your application with Azure AD as well as automatically configure your application to integrate with Azure AD.

What type of authentication is used in MVC?

During application crafting MVC asks for authentication that includes the following. No Authentication: It is used to set no authentication for the application. It allows anonymous user to access. Individual User Accounts: It is mostly used and common approach to set authentication for the application.


1 Answers

To support both individual accounts and other account from social data provider, only need to add them using OWIN component.

And to sign-out the users which login from Azure AD, we need to sign-out both the cookie issued from web app and Azure AD. First, I modified the ApplicationUser class to add the custom claim to detect whether the users login from Azure AD or individual 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;
    }
}

Then we can change the LogOff method to support sign-out from Azure AD to clear the 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");
}
like image 109
Fei Xue - MSFT Avatar answered Oct 19 '22 03:10

Fei Xue - MSFT