Search code examples
c#authenticationasp.net-mvc-5azure-active-directoryasp.net-identity

Authenticate using Azure AD but Authorize using aspnetmembership roles and claims


I am trying to build an ASP.NET MVC 5 application that uses Azure AD for authentication. But once the user is authenticated, I need to use aspnet_membership Microsoft Identity set up to grab the claims for that authenticated logged in user. We don't want to maintain roles and claims within the Azure AD setup and we don't want to use MS Graph.

I have created one MVC 5.0 project using Individual User Accounts in VS 2017, which in turn created the aspnet_membership database in my SQL Server database for me.

I have also created a separate MVC 5.0 project and registered the app in Azure AD and I have the ClientID etc. and that project is also working fine. Now I am trying to merge the two and I am kind of getting lost and doubting if I am thinking it right.

Basically once the user logs in on that Microsoft Azure AD login page, I redirect to a local registration page where when the user registers with just bare minimum info including some roles, and I would then make an entry in the AspNetUsers/Claims tables and I have to attach those claims to the Principal. On subsequent logins for that user, I have to load the clams once authenticated.

Can you please help me in pointing to any samples for this kind of a scenario, as most of what I have read here advice to use Microsoft Graph. But our roles are way too complicated and we have decided to use the local identity aspnet_membership database only for authorization (Roles as Claims).

Thanks


Solution

  • This worked for me.

    public partial class Startup
        {       
            public void ConfigureAuth(IAppBuilder app)
            {
                string clientId = System.Configuration.ConfigurationManager.AppSettings["ClientId"];
                string redirectUri = System.Configuration.ConfigurationManager.AppSettings["RedirectUri"];
                string tenant = System.Configuration.ConfigurationManager.AppSettings["Tenant"];
                string authority = string.Format(System.Configuration.ConfigurationManager.AppSettings["Authority"], tenant);
                var cookieExpiryHours = Int32.Parse(System.Configuration.ConfigurationManager.AppSettings["CookieExpiryHours"]);
    
                app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
                app.UseCookieAuthentication(new CookieAuthenticationOptions
                {
                    CookieManager = new SystemWebCookieManager(),
                    ExpireTimeSpan = TimeSpan.FromMinutes(cookieExpiryHours), 
                    SlidingExpiration=true,             
                });
    
                app.UseOpenIdConnectAuthentication(
                    new OpenIdConnectAuthenticationOptions
                    {                   
                        ClientId = clientId,
                        Authority = authority,
                        RedirectUri = redirectUri,                   
                        PostLogoutRedirectUri = redirectUri,
                        Scope = OpenIdConnectScope.OpenIdProfile,
                        UseTokenLifetime = false,
                        // ResponseType is set to request the code id_token - which contains basic information about the signed-in user
                        ResponseType = OpenIdConnectResponseType.CodeIdToken,                    
                        TokenValidationParameters = new TokenValidationParameters()
                        { 
                            ValidateIssuer = true
                        },                  
                      
                        Notifications = new OpenIdConnectAuthenticationNotifications
                        {
                            AuthenticationFailed = OnAuthenticationFailed,
                            SecurityTokenValidated = OnSecurityTokenValidated,
                        }
                    }
    
                );
             
            }
    
            private Task OnSecurityTokenValidated(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
            {
                var OIDClaimDesc = "http://schemas.microsoft.com/identity/claims/objectidentifier";
                var claims = context.AuthenticationTicket.Identity.Claims;
                var cookieExpiryHours = Int32.Parse(System.Configuration.ConfigurationManager.AppSettings["CookieExpiryHours"]);
    
                context.AuthenticationTicket.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddHours(cookieExpiryHours);
                context.AuthenticationTicket.Properties.IsPersistent = false;
    
                var owinContext = context.OwinContext;
                var userIdentity = context.AuthenticationTicket.Identity;
                var userClaims = userIdentity as ClaimsIdentity;
                
                var firstName = userClaims?.FindFirst(ClaimTypes.GivenName)?.Value ?? string.Empty;
                var lastName = userClaims?.FindFirst(ClaimTypes.Surname)?.Value ?? string.Empty;
                var email = userClaims?.FindFirst(ClaimTypes.Email)?.Value ?? string.Empty;
                var objID = Guid.Parse(userClaims.FindFirst(OIDClaimDesc).Value);
                var user = new UserService().GetUser(objID, email);
    
                if (user is null)//This user has just wandered in to the site or the admins have not added this user in the DB yet. Just redirect them back to log out
                {
                    owinContext.Authentication.Challenge();
                    return Task.FromResult(0);
                }
    
                if (userIdentity.IsAuthenticated)
                {
                    userIdentity.AddClaim(new Claim(ClaimTypes.GivenName, firstName));
                    userIdentity.AddClaim(new Claim(ClaimTypes.Surname, lastName));
                    userIdentity.AddClaim(new Claim(ClaimTypes.Email, email));
                    userIdentity.AddClaim(new Claim("AzureID", objID.ToString()));
                    userIdentity.AddClaim(new Claim(ClaimTypes.Role, "user"));
                }
    
                new UserService().UpdateUser(objID, firstName, lastName, email);
    
                foreach (var claim in user.UserClaims)
                {
                    if (!claim.ClaimType.Equals(ClaimTypes.GivenName, StringComparison.OrdinalIgnoreCase)
                        && !claim.ClaimType.Equals(ClaimTypes.Surname, StringComparison.OrdinalIgnoreCase))
                    {
                        userIdentity.AddClaim(new Claim(ClaimTypes.Role, claim.ClaimValue));
                    }
                }
    
                return Task.FromResult(0);
            }
    
            /// <summary>
            /// Handle failed authentication requests by redirecting the user to the home page with an error in the query string
            /// </summary>
            /// <param name="context"></param>
            /// <returns></returns>
            private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
            {
                var code = "IDX21323";
                if (context.Exception.Message.Contains(code)) //I need this, as under certain conditions, the auth process was going on an infinite loop. 
                {
                    context.HandleResponse();
                    context.OwinContext.Authentication.Challenge();
                }
    
                return Task.FromResult(true);
            }
        }
    
    public UserViewModel GetUser(Guid guid, string email)
            {
                var model = new UserViewModel();
                using (var ctxt = new DBContext())
                {
                    var user = ctxt.Users.Where(x => (x.Email == email || x.OID==guid) && x.IsActive).FirstOrDefault();
                    if (user == null)
                        return null;
                    var claims = ctxt.UserClaims.Where(x => x.UserId==user.ID).ToList();
                    model = Mapper.Map<UserViewModel>(user);
                    model.UserClaims = Mapper.Map<List<ViewModels.UserClaimViewModel>>(claims);
                }
    
    
                return model;
            }