Showing posts with label ASP.NET MVC. Show all posts
Showing posts with label ASP.NET MVC. Show all posts

Tuesday, 3 December 2013

Using External Login Providers with ASP.NET Identity System

In last post, we saw how simple the new Identity system in ASP.NET is and we explored the code generated by the Visual Studio template for handling local user accounts. In this post, we will see how easy it is to use an external login provider with the identity system.

Allowing users to use an external provider to login to a website has several advantages. It includes:

  1. Your application doesn’t need to store user names and passwords of the users using external login
  2. Almost all of the external login providers have secured logging page, using HTTPS
  3. The user doesn’t need to register to your site and so he/she doesn’t need to remember another set of user name and password


The Startup.Auth.cs file contains a block of commented code for external login providers. The providers include Google, Microsoft, Twitter and Facebook.

// Uncomment the following lines to enable logging in with third party login providers
//app.UseMicrosoftAccountAuthentication(
//    clientId: "",
//    clientSecret: "");

//app.UseTwitterAuthentication(
//   consumerKey: "",
//   consumerSecret: "");

//app.UseFacebookAuthentication(
//   appId: "",
//   appSecret: "");

//app.UseGoogleAuthentication();

Out of these four providers, Google is the easiest provider to configure and use. It is as easy as just uncommenting the last statement in the above commented snippet. For other providers, we need to visit developer help websites of the third parties and input details of the application to get the keys. Pranav Rastogi documented these steps for us; they are available on Web Development Tools Blog on MSDN.

I enabled Google and Twitter in my application. The login page shows up buttons to use these providers.

After logging in using one of the external services, the application would ask the user to set a local user name. The user can optionally also set a local password for the account. Once the login at the external provider is successful, the user would be directed to ExternalLoginCallback. Following is the code inside this action method:
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{         
    var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
    if (loginInfo == null)
    {
        return RedirectToAction("Login");
    }

    // 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)
    {
        await SignInAsync(user, isPersistent: false);
        return RedirectToLocal(returnUrl);
    }
    else
    {
        // If the user does not have an account, then prompt the user to create an account
        ViewBag.ReturnUrl = returnUrl;
        ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
        return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { UserName = loginInfo.DefaultUserName });
    }
}
It does the following:

  1. Checks if the external login is successful. If the login has failed, the user is directed to the login page
  2. Once the login is successful, it checks if the logged in user already has a local username in the application. If the user doesn’t have one, the application prompts the user to create one
  3. If the user already has an account, the application directs the user to the return URL

To identify the user when he/she comes back and logs in using the same third party account, the identity system maintains details in the following two tables:

  1. AspNetUsers: It is the same table that holds details of local users. By default, external user IDs would be created with empty passwords.
  2. AspNetUserLogins: Holds user ID, name of the external provider and a provider key

The code of creating these records can be found in the ExternalLoginConfirmation action method of the AccountController. Following is the snippet that adds the records:

var info = await AuthenticationManager.GetExternalLoginInfoAsync();
if (info == null)
{
    return View("ExternalLoginFailure");
}
var user = new ApplicationUser() { UserName = model.UserName };
var result = await UserManager.CreateAsync(user);
if (result.Succeeded)
{
    result = await UserManager.AddLoginAsync(user.Id, info.Login);
    if (result.Succeeded)
    {
        await SignInAsync(user, isPersistent: false);
        return RedirectToLocal(returnUrl);
    }
}

The AuthenticationManager.GetExternalLoginInfoAsync called above is a generic method and it provides just the data needed to identify the user. The returned value contains default user name, name of the third party provider and an authentication ID. It doesn’t provide vendor specific details about a user. To get more details from the vendor, we need to use the AuthenticateAsync method.
var vendorResult = await AuthenticationManager.AuthenticateAsync(DefaultAuthenticationTypes.ExternalCookie);

Following screenshot shows the details obtained after logging in using twitter. If you inspect the value returned from Google, you will find e-mail ID of the user in the details, you may use it as the default username for the application as well.

Happy coding!

Saturday, 30 November 2013

A Look at the new Identity System in ASP.NET

One of the key features added to ASP.NET core with the release of Visual Studio 2013 is the new Identity System. If you create a new ASP.NET project, may it be a Web Forms or an MVC project, you will find the default authentication type selected as "Individual User Accounts".



The individual user authentication type creates authentication system based on the Identity system, which looks greatly simplified when compared with the Membership system we had in earlier version. The default project template includes the following references (also available via NuGet packages with same names):

  • Microsoft.AspNet.Identity.Core: Consists of the core classes and interfaces for identity system
  • Microsoft.AspNet.Identity.EntityFramework: Consists of classes implemented for Identity system using ADO.NET Entity Framework

The default templates make use of Entity Framework to persist the user’s information in a SQL Server database. The default database used is an MDF file.

Classes and Setup:
The project template includes Owin and Katana. In the authorization configuration, we see the following setings applied to setup cookie based authentication system on Owin:

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    LoginPath = new PathString("/Account/Login")
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

It makes the authentication system sharable across Web forms, MVC, Web API and SignalR without adding any additional code. The authentication configuration is called from Owin start-up class. In the models folder of the project, a file with following code is added for us:
public class ApplicationUser : IdentityUser
{
}

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {
    }
}

As we see, this file includes two classes:

  • ApplicationUser: Inherited from Microsoft.AspNet.Identity.EntityFramework.IdentityUser. The IdentityUser class is inherited from the interface IUser. Any user class in the identity system must inherit from this interface. The class IdentityUser includes all necessary properties needed for a user account, like UserName, PasswordHash, SecurityStamp. This class allows us to add our own properties to a user’s profile. For example, if your web site needs to capture e-mail ID and phone number of the user, you can add them to this page. The IdentityUser class also includes navigation properties for UserRoles, UserClaims and UserLogins. The UserLogin entity is used to store information when a user is logged in using an external authentication provider, like Google, Microsoft, Twitter or Facebook.
  • ApplicationDbContext: It is the Entity Framework code-first DbContext class to create the database with necessary tables. It is extended from IdentityDbContext, the DbContext class defined in Microsoft.AspNet.Identity.EntityFramework namespace. It includes DbSets for User and Role. This class allows us to add our own DbSets to the database being created.

Once the application is executed, a SQL server MDF file is created with the following tables:




User management:
To manage the users and their data, the application uses the following two classes:

  • UserStore: A class in Microsoft.AspNet.Identity.EntityFramework namespace. It is responsible for all database operations related to managing users. It needs a DbContext for its work; we generally pass in an instance of the IdentityDbContext. This class implements six interfaces: IUserStore, IUserPasswordStore, IUserLoginStore, IUserClaimStore, IUserRoleStore and IUserSecurityStampStore. Each of them has a specific purpose. IUserStore is for creating, finding, updating and deleting the user information; IUserPasswordStore is for managing password and so on. While writing a custom Identity system, IUserStore is the least interface to be implemented. All methods declared in all of these interfaces are asynchronous and they return Task.
  • UserManager: This class is defined in the Microsoft.AspNet.Identity.Core assembly. It needs an instance of IUserStore type. The UserManager can be viewed as a repository that calls specific methods of UserStore to manage users for the application. As in UserStore, methods of UserManager are also asynchronous.
The UserManager is instantiated as follows:
var UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));

Let’s see how a new user registration works. In the AccountController of the MVC project, we find the following code in the Register action method (Similar code can be found in Register.aspx.cs of Web Forms project):
var user = new ApplicationUser() { UserName = model.UserName };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
    await SignInAsync(user, isPersistent: false);
    return RedirectToAction("Index", "Home");
}

The UserManager.CreateAsync() method calls UserStore.CreateAsync to store the user information in Database. Once the registration is succeeded, a call to SignInAsync is made to authenticate the user. Following is the code inside SignInAsync method:
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);

The AuthenticationManager used here is the Authentication object of the current Owin context. The first statement clears any external cookies in the current application context. Then it asks the UserManager to create a claims-based identity for the current user and then the obtained claims-based identity object is set to the current Owin context.

The Login action method differs by just one statement, it is shown below:

var user = await UserManager.FindAsync(model.UserName, model.Password);

The FindAsync method returns null if the user’s credentials are not valid. If credentials are valid, it returns all information about the user. Once the user is found, a claims-based identity object of the user is set to the Owin context.

LogOff is a straight forward implementation. It just calls the SignOut method of the Authentication object of the Owin context:

Context.GetOwinContext().Authentication.SignOut();

Similarly, the template also generates code for changing password, removing user account and to handle external login accounts. We will see how the identity system manages external logins in a future post.

Happy coding!