ASP.NET Core 5.0 : MVC, Razor Pages, Web API, EF Core, Blazor, Design Patterns, and more. Private online coaching for software developers. Click here for more details.

Integrate ASP.NET Core Identity with Microsoft account

In the previous article you learned to use Microsoft Account as an external login to your ASP.NET Core web apps. In that article you didn't use ASP.NET Core Identity in any way. The Microsoft Account alone was used in the authentication process without any local account. At times you may want to integrate the Microsoft account with a local account.

Suppose you want to implement role based security in your web app. The authentication will happen using Microsoft account but you further want to grant access based on roles that are local to the web app. In such cases, you are integrate ASP.NET Code Identity with Microsoft account. The application roles and user to role mappings will reside in the ASP.NET Core Identity. Upon successful sign in with Microsoft account you will create a local account that links to the Microsoft account. The local account will have necessary role information. Once you sign in using Microsoft account the role based security is provided by ASP.NET Core Identity.

In this article you will modify the previous example to use ASP.NET Core Identity. So, make sure you have previous code ready in Visual Studio. I am also going to assume that you have the necessary tables ready in a SQL Server database used by ASP.NET Core Identity.

Let's get started!

Open Startup class and modify the ConfigureServices() method as shown below:

public void ConfigureServices(
IServiceCollection services)
{

    services.AddControllersWithViews();
    services.AddRazorPages();

    services.AddDbContext<ApplicationDbContext>
(options => options.UseSqlServer(
            Configuration.GetConnectionString
("DefaultConnection")));
    services.AddDatabaseDeveloperPageExceptionFilter();

    services.AddIdentity<IdentityUser,IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>();

    services.AddAuthentication()
            .AddMicrosoftAccount(o =>
            {
                o.ClientId = Configuration
["Authentication:ClientId"];
                o.ClientSecret = Configuration
["Authentication:ClientSecret"];
            });
}

In this code, we first registered the ApplicationDbContext that is responsible for data access required by ASP.NET Core Identity. The ApplicationDbContext class looks like this:

public class ApplicationDbContext : 
IdentityDbContext
{
    public ApplicationDbContext
(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
}

The DefaultConnection is the database connection string stored in appsettings.json file:

"ConnectionStrings": {
    "DefaultConnection": "data source=.;initial 
catalog=northwind111;integrated security=true"
}

Make sure to modify the connection string as per your database environment.

Then the code calls AddIdentity() and AddEntityFrameworkStores() methods. These methods use IdentityUser, IdentityRole, and ApplicationDbContext classes. The IdentityUser and IdentityRole classes are provided by ASP.NET Core Identity itself.

Then we call AddAuthentication() and AddMicrosoftAccount() methods. If you followed the earlier article, these calls should look familiar to you.

Next, open the HomeController and add a constructor to it as shown below:

public class HomeController : Controller
{
    private readonly SignInManager<IdentityUser> 
signInManager;
    private readonly UserManager<IdentityUser> 
userManager;
    private readonly RoleManager<IdentityRole> 
roleManager;

    public HomeController(
        SignInManager<IdentityUser> signInManager,
        UserManager<IdentityUser> userManager,
        RoleManager<IdentityRole> roleManager)
    {
        this.signInManager = signInManager;
        this.userManager = userManager;
        this.roleManager = roleManager;
    }
    ...
    ...
}

We declare a few member variables inside the HomeController class - SignInManager, UserManager, and RoleManager. These objects are injected in the constructor and are used by the code we are going to write soon.

Now, go to SignIn() action and modify it as follows:

public IActionResult SignIn()
{
    var properties = signInManager.
ConfigureExternalAuthenticationProperties
(MicrosoftAccountDefaults.AuthenticationScheme, 
"/Home/SignInSuccess");

    return Challenge(properties, 
MicrosoftAccountDefaults.AuthenticationScheme);
}

Here, we call the ConfigureExternalAuthenticationProperties() method of SignInManager to configure an AuthenticationProperties object. Then we pass AuthenticationProperties object and the authentication scheme to the Challenge() method.

Upon successful sign-in the control will go to SignInSuccess() action. This action is shown below:

public async Task<IActionResult> SignInSuccess()
{
    var info = await signInManager.GetExternalLoginInfoAsync();

    var result = await signInManager.
ExternalLoginSignInAsync(info.LoginProvider, 
info.ProviderKey, 
isPersistent: false, 
bypassTwoFactor: true);

    if (result.Succeeded)
    {
        return RedirectToAction("Index");
    }
    else
    {
        var email = info.Principal.FindFirstValue
(ClaimTypes.Email);

        var user = new IdentityUser
        {
            UserName = email,
            Email = email
        };

        var userResult = await userManager.CreateAsync(user);

        if (userResult.Succeeded)
        {
            userResult = await userManager.
AddLoginAsync(user, info);

            if(!await roleManager.RoleExistsAsync("Admin"))
            {
                IdentityRole role = new IdentityRole("Admin");
                await roleManager.CreateAsync(role);
            }

            await userManager.AddToRoleAsync(user, "Admin");

            if (userResult.Succeeded)
            {
                await signInManager.SignInAsync(user, 
isPersistent: false, 
info.LoginProvider);
                return RedirectToAction("Index");
            }
        }
        return RedirectToAction("Index");
    }
}

The code calls GetExternalLoginInfoAsync() method of SignInManager to retrieve external login information such as login provider and its key.

We then call ExternalLoginSignInAsync() method by passing provider name and key. Initially this method will fail because there is no associated local login yet.

If there is no local account for this user, we need to create it. This is done using CreateAsync() method of UserManager. The CreateAsync() method accepts an IdentityUser object representing the new user. Notice how user name and email are obtained from ExternalLoginInfo object.

After the user account is created successfully, we call AddLoginAsync() method to add an external login entry in the Identity store. You will find that CreateAsync() adds an entry into the AspNetUsers table whereas AddLoginAsync() adds an entry to AspNetUserLogins table.

We then proceed to check whether Admin role exists in the database or not. If it doesn't exist, we create it using CreateAsync() method of RoleManager. Once the role is created, we add the user to Admin role using AddToRoleAsync() method of UserManager.

Then we sign the user using SignInAsync() method of SignInManager. Finally, we redirect the control to Index page.

No test whether the role based security is working as expected modify the Privacy() action like this:

[Authorize(Roles = "Admin")]
public IActionResult Privacy()
{
    return View();
}

Now the [Authorize] attribute specifies that the user must belong to Admin role.

The final change is in the SignOut() action. Earlier we used HttpContext.SignOutAsync() method. Now we will use SignOutAsync() of SignInManager.

public async Task<IActionResult> SignOut(string signOutType)
{
    if (signOutType == "app")
    {
        await signInManager.SignOutAsync();
    }
    ...
    ...
}

Set a break point in the SignInSuccess() action and run the application. Check the execution step-by-step to understand how the local account gets created and role is assigned to the user.

That's it for now! Keep coding!!


Bipin Joshi is an independent software consultant, trainer, author, yoga mentor, and meditation teacher. He has been programming, meditating, and teaching for 24+ years. He conducts instructor-led online training courses in ASP.NET family of technologies for individuals and small groups. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Ajapa Yoga to interested individuals. To know more about him click here.

Get connected : Facebook  Twitter  LinkedIn  YouTube

Posted On : 19 October 2020


Tags : ASP.NET ASP.NET Core Data Access SQL Server MVC C# Visual Studio


Subscribe to our newsletter

Get monthly email updates about new articles, tutorials, code samples, and how-tos getting added to our knowledge base.

  

Receive Weekly Updates