How to use both Azure AD authentication and Identity on ASP.NET Core 3?

喜夏-厌秋 提交于 2020-07-06 12:05:17

问题


The web application should allow internal employees with AD accounts to authenticate in the app using Azure AD Authentication. External users should be able to register and sign in using ASP.NET Core Identity. I can implement each one separately but not together in the same app. When I add both authentications to the same app, the ASP.NET Core Identity works perfectly. I can register and log in using Identity with no problem. However when I try to log in with Azure AD, the app redirects me to my tenant's login page, I submit a username and password, it redirects me back to the application but no user is authenticated. I hit the login button again and the same thing happens. It seems that the web app or browser is not saving the access token or something like that.

What am I doing wrong? Is it even possible to have two sets of authentication on the same app?

Thanks. Here's the code:

Packages

<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="3.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.1">

Startup Class

public IConfiguration Configuration { get; }

public Startup(IConfiguration configuration) => Configuration = configuration;

public void ConfigureServices(IServiceCollection services)
{
    //  Add Azure AD authentication
    services.AddAuthentication(defaultScheme: AzureADDefaults.AuthenticationScheme)
        .AddAzureAD(options => Configuration.Bind("AzureAd", options));

    //  Add the application db context
    services.AddDbContext<AppDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    //  Add Identity using Entity Framework Core
    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<AppDbContext>()
        .AddDefaultTokenProviders();

    //  Configure Identity
    services.Configure<IdentityOptions>(options =>
    {
        // Password settings.
        options.Password.RequireDigit = true;
        options.Password.RequireLowercase = true;
        options.Password.RequireNonAlphanumeric = true;
        options.Password.RequireUppercase = true;
        options.Password.RequiredLength = 6;
        options.Password.RequiredUniqueChars = 1;

        // Lockout settings.
        options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
        options.Lockout.MaxFailedAccessAttempts = 5;
        options.Lockout.AllowedForNewUsers = true;

        // User settings.
        options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
        options.User.RequireUniqueEmail = true;
    });

    services.AddMvc();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseDeveloperExceptionPage();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(endpoints => endpoints.MapControllers());
}

User Controller

This is a custom controller where we handle HTTP requests related to authentication.

private readonly UserManager<ApplicationUser> userManager;
private readonly SignInManager<ApplicationUser> signInManager;

public UserController(UserManager<ApplicationUser> um, SignInManager<ApplicationUser> sm) =>
     (userManager, signInManager) = (um, sm);

//  Internal employee users will authenticate using Azure AD
[HttpGet("internal-signin")]
public ChallengeResult InternalSignIn(string returnUrl = "/") => 
     Challenge(new AuthenticationProperties { RedirectUri = returnUrl }, AzureADDefaults.AuthenticationScheme);

//  Display view with a form to create a new external user account
[HttpGet("register")]
public ViewResult Register() => View();

//  Create a new account for an external user
[HttpPost("register")]
public async Task<IActionResult> Register(RegistrationInputModel inputModel)
{
    //  Check if the model state is valid
    if (!ModelState.IsValid)
    {
        //  Redirect to the Register view
        return View(viewName: nameof(Register), model: inputModel);
    }

    //  Create an application user object
    ApplicationUser user = new ApplicationUser
    {
        //  Map the fields of the input model with the user
        UserName = inputModel.Email,
        Email = inputModel.Email,
        FirstName = inputModel.FirstName,
        LastName = inputModel.LastName,
        Company = inputModel.CompanyName,
    };

    //  Try to register the user on the database
    IdentityResult result = await userManager.CreateAsync(user, inputModel.Password);

    //  If failed, then set the error messages into the model state
    if (!result.Succeeded)
    {
        foreach (IdentityError error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }

        //  Return the user to the registration view
        return View(viewName: nameof(Register), model: inputModel);
    }

    //  Sign In the user
    await signInManager.SignInAsync(user, isPersistent: false);

    //  Otherwise, redirect the user to the index page
    return RedirectToAction(nameof(HomeController.Index), controllerName: "Home");
}

//  External users sign out action
[HttpGet("signout")]
[Authorize]
public async Task<IActionResult> SignOut()
{
    await signInManager.SignOutAsync();
    return RedirectToAction(nameof(HomeController.Index), "Home");
}

//  Display form to login for external users
[HttpGet("signin")]
public ViewResult SignIn() => View();

//  Login an external user
[HttpPost("signin")]
public async Task<IActionResult> SingIn(SingInInputModel inputModel)
{
    //  Check if the model state is valid
    if (!ModelState.IsValid)
    {
        //  Send the user back to the sign in view
        return View(viewName: nameof(SignIn), model: inputModel);
    }

    //  Try to sign in the user
    SignInResult result = await signInManager
        .PasswordSignInAsync(inputModel.Email, inputModel.Password, inputModel.RememberMe, lockoutOnFailure: false);

    //  Check if the login was unsuccessful
    if (!result.Succeeded)
    {
        ModelState.AddModelError(string.Empty, "Invalid login attempt.");
        return View(viewName: nameof(SignIn), model: inputModel);
    }

    //  Send the user back to the index page
    return RedirectToAction(nameof(HomeController.Index), "Home");
}

Application User

public class ApplicationUser : Microsoft.AspNetCore.Identity.IdentityUser
{
     public string FirstName { get; set; }
     public string LastName { get; set; }
     public string Company { get; set; }
}

回答1:


If using ASP.NET Core Identity with Azure AD login , you can set CookieSchemeName to Identity.External so that asp.net core identity can get the external user profile from external identity provider , and create a local user associated with external user :

In appsettings.json :

"AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "peterpad.onmicrosoft.com",
    "TenantId": "cb1c3f2e-a2dd-4fde-bf8f-f75ab18b21ac",
    "ClientId": "0c0ec562-a9bb-4722-b615-6dcbdc646326",
    "CallbackPath": "/signin-oidc",
    "CookieSchemeName": "Identity.External"
},

Then if you want to challenge Azure AD login in MVC controller , you should provide the scheme name , config redirect url after authentication to Identity/Account/ExternalLoginand Callbackhandler , in that handler asp.net core identity will let your enter username and create a local user :

[HttpGet("internal-signin")]
public ChallengeResult InternalSignIn(string returnUrl = "/") 
{
    var redirectUrl = Url.Page("/Account/ExternalLogin", pageHandler: "Callback", values: new { returnUrl , area = "Identity" });
    var properties = _signInManager.ConfigureExternalAuthenticationProperties(AzureADDefaults.AuthenticationScheme, redirectUrl);
    return new ChallengeResult(AzureADDefaults.AuthenticationScheme, properties);
}


来源:https://stackoverflow.com/questions/60198623/how-to-use-both-azure-ad-authentication-and-identity-on-asp-net-core-3

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!