Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to have both Azure AD and Individual Account authentication in one ASP.NET MVC application?

I am kind of successful by doing this in the Startup.Auth.cs file

  // Configure the db context and user manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.Properties["Microsoft.Owin.Security.Constants.DefaultSignInAsAuthenticationType"] = "ExternalCookie";

        // Configure the sign in cookie
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
             Provider = new CookieAuthenticationProvider
            {
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
            }
        });

        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

        app.UseOpenIdConnectAuthentication(
          new OpenIdConnectAuthenticationOptions
          {
              ClientId = clientId,
              Authority = authority,
              PostLogoutRedirectUri = postLogoutRedirectUri
          });

The challenge I have is, when a user is signed out,and tries to hit a non-login page like say http://mywebsite/users/management rather than http://mywebsite/account/login the application redirects to the Azure AD sign-in page automatically, which is not right. Because there could be users who do not have account on Azure AD at all. Even if we give a proper userid and password in the AD sign in page and click sign-in, it keeps redirecting between different urls within http://login.windows.net and never goes to our website at all.

Here is the logout code -

           AuthenticationManager.SignOut(new string[] { DefaultAuthenticationTypes.ExternalCookie, DefaultAuthenticationTypes.ApplicationCookie, OpenIdConnectAuthenticationDefaults.AuthenticationType });
        return RedirectToAction("Login", "Account");

I am not sure what I'm doing wrong here.

Edit 1 My ExternalLoginCallback method

public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
    {
        var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
        if (loginInfo == null)
        {
            return RedirectToAction("Login");
        }

        var claims = new List<Claim>();
        claims.Add(new Claim(ClaimTypes.Sid, "Office365"));

        // Sign in the user with this external login provider if the user already has a login

        var user = await UserManager.FindByEmailAsync(loginInfo.ExternalIdentity.Name);

        if (user != null && user.IsActive == true && user.EmailConfirmed == true)
        {
            var result = await UserManager.AddLoginAsync(user.Id, loginInfo.Login);

            if (result.Succeeded)
            {
                if (claims != null)
                {
                    var userIdentity = await user.GenerateUserIdentityAsync(UserManager);
                    userIdentity.AddClaims(claims);
                }
            }

            await SignInAsync(user, isPersistent: true);
            Session[AppConstants.General.UserID] = user.Id;

            string fullName = string.Format("{0} {1}",user.FirstName,user.LastName);
            Session[AppConstants.General.UserFullName] = fullName;

            return RedirectToLocal(returnUrl);
        }
        else
        {
            // If the user does not have an account, tell that to the user.
            ViewBag.ReturnUrl = returnUrl;
            ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
            return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
        }
    }
like image 700
Imaya Kumar Avatar asked Mar 12 '15 10:03

Imaya Kumar


1 Answers

Try this

app.UseOpenIdConnectAuthentication(
           new OpenIdConnectAuthenticationOptions
           {
               ClientId = ClientId,
               Authority = Authority,                   
               Notifications = new OpenIdConnectAuthenticationNotifications()
               {


                   RedirectToIdentityProvider = (context) =>
                   {

                       if (context.Request.Path.Value == "/Account/ExternalLogin" || (context.Request.Path.Value == "/Account/LogOff" && context.Request.User.Identity.IsExternalUser()))
                       {
                           // This ensures that the address used for sign in and sign out is picked up dynamically from the request
                           // this allows you to deploy your app (to Azure Web Sites, for example)without having to change settings
                           // Remember that the base URL of the address used here must be provisioned in Azure AD beforehand.
                           string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
                           context.ProtocolMessage.RedirectUri = appBaseUrl + "/";
                           context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
                       }
                       else
                       {
                           //This is to avoid being redirected to the microsoft login page when deep linking and not logged in 
                           context.State = Microsoft.Owin.Security.Notifications.NotificationResultState.Skipped;
                           context.HandleResponse();
                       }
                       return Task.FromResult(0);
                   },
               }
           });

EDIT:

Forgot this extension method

    public static class IdentityExtensions
{
    public static bool IsExternalUser(this IIdentity identity)
    {
        ClaimsIdentity ci = identity as ClaimsIdentity;

        if (ci != null && ci.IsAuthenticated == true)
        {
            var value = ci.FindFirstValue(ClaimTypes.Sid);
            if (value != null && value == "Office365")
            {
                return true;
            }
        }
        return false;
    }
}

EDIT 2:

You have to have some custom logic in the ExternalLoginCallback (AccountController) e.g. add the Sid claim. In this case there is also logic to check if the user allows external login.

 // GET: /Account/ExternalLoginCallback
    [AllowAnonymous]
    public async Task<ActionResult> ExternalLoginCallback(string returnUrl, string urlHash)
    {
        var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
        if (loginInfo == null)
        {
            return RedirectToAction("Login");
        }

        var claims = new List<Claim>();
        claims.Add(new Claim(ClaimTypes.Sid, "Office365"));

        // Sign in the user with this external login provider if the user already has a login
        var user = await UserManager.FindAsync(loginInfo.Login);
        if (user == null)
        {
            user = await UserManager.FindByNameAsync(loginInfo.DefaultUserName);

            if (user != null)
            {
                if(user.AllowExternalLogin == false)
                {
                    ModelState.AddModelError("", String.Format("User {0} not allowed to authenticate with Office 365.", loginInfo.DefaultUserName));
                    return View("Login");
                }
                var result = await UserManager.AddLoginAsync(user.Id, loginInfo.Login);

                if (result.Succeeded)
                {
                    if (claims != null)
                    {
                        var userIdentity = await user.GenerateUserIdentityAsync(UserManager);
                        userIdentity.AddClaims(claims);
                    }
                    await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
                }
                return RedirectToLocal(returnUrl);
            }
            else
            {
                ModelState.AddModelError("", String.Format("User {0} not found.", loginInfo.DefaultUserName));
                return View("Login");
            }
        }
        else
        {

            if (user.AllowExternalLogin == false)
            {
                ModelState.AddModelError("", String.Format("User {0} not allowed to authenticate with Office 365.", loginInfo.DefaultUserName));
                return View("Login");
            }

            if (claims != null)
            {
                var userIdentity = await user.GenerateUserIdentityAsync(UserManager);
                userIdentity.AddClaims(claims);
            }
            await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
            return RedirectToLocal(returnUrl);
        }
    }
like image 150
Kjartan Valur Þórðarson Avatar answered Oct 17 '22 09:10

Kjartan Valur Þórðarson