CodeDigest.Com Logo
Featured:

Using Microsoft Login for Authentication in Asp.Net MVC 5.0 – OAuth 2.0

Tagged as: Asp.Net MVC Posted By

My previous articles Integrate Google (Gmail) Authentication in Asp.Net MVC 5.0 - OAuth 2.0 and Use LinkedIn Login for Authentication in Asp.Net MVC 5.0 – OAuth 2.0 discussed about integrating Gmail id and LinkedIn id for authenticating users in Asp.Net MVC application using OAuth 2.0. In this article, let’s see how we can integrate Microsoft authentication in Asp.Net MVC site. We will use same DotNetOpenAuth components in this article too to develop this feature.

Microsoft OAuth authentication flow is similar to Google and LinkedIn flows. To recap,

  1. Users will be redirected to Microsoft Login page from Asp.Net MVC application.

  2. User Enters Microsoft Username (your Hotmail or live id) and Password. User allows the third party (our MVC application) application access to read the Microsoft profile details.

  3. Microsoft OAuth module redirects the user to a pre-configured landing page on the application by passing access code.

  4. Application passes access code and queries the Microsoft OAuth API to get the access token and some default user details. Application can then call Microsoft API to get any additional user details like email, etc. With this details, application can create a new user in local database and issue a forms authentication ticket.

Creating OAuth Microsoft application

  1. Go to https://apps.dev.microsoft.com/ . Sign-in with your hotmail id.

  2. Click “Add an app” button to create new OAuth 2.0 application.

  1. Enter a new application name and click Create Application.

  1. This will present a screen to input details of our application. It also display the Application ID and a button called “Generate New Password” to generate secret. Click this button to see the secret key, please store it securely as it is only shown for one time.

  1. Click “Add Platform” button to add details about your application. Click “Web” tile as we are developing a Asp.Net MVC web application.

  2. Add the application redirect url or the landing page url where the user needs to redirected after successful authentication.

You can add multiple redirect url for an application by clicking Add URL button

  1. Click Save button to save the details.

Let’s move ahead and create our MVC project.

Creating New Project

  1. Open Visual Studio (2012/2015) and create a new Asp.Net MVC project. Select Empty project template to add the required Nuget package ourselves. I have used “Asp.Net MVC 5 Empty Project”

  1. Add the below Nuget packages into your solution.

    1. DotNetOpenAuth.AspNet

    2. Microsoft.AspNet.Membership.OpenAuth

    3. Microsoft.AspNet.Providers.LocalDB

Note – For easy understanding, we will use Microsoft.AspNet.Providers.LocalDB Asp.Net Universal Provider to create and persist user in localDb under App_data. You can call your User data access code to create user once the provider calls the landing page with user details.

  1. Add a new class file AuthConfig.cs under App_Start Folder. Let’s configure and add the Microsoft OAuth 2 client into the OpenAuth AuthenticationClients collection. This is where we will configure the Application Id and Client secret we generated in previous section to connect Microsoft OAuth 2.0 API.

 

public static class AuthConfig
{
    public static void RegisterAuth()
    {
        if (OpenAuth.AuthenticationClients.GetByProviderName("microsoft") == null)
        {
            MicrosoftClient msClient = new MicrosoftClient("[App Id]", "[Secret]");
            OpenAuth.AuthenticationClients.Add("microsoft", () => msClient);
        }
    }
}
 

Include Microsoft.AspNet.Membership.OpenAuth namespace in using section for the above code to work.

Note – You can add multiple OAuth provider clients like Google, FaceBook, Twitter, etc. here. You can get the registered OAuth clients by access OpenAuth.AuthenticationClients collection property across application. The default project template’s AccountController code uses this collection when you select “Individual User Accounts” or “Internet Applications” as template when creating new projects.

  1. Call this method from Global.asax Application_Start event to register the OAuth clients during app startup.

protected void Application_Start()

{

AreaRegistration.RegisterAllAreas();

RouteConfig.RegisterRoutes(RouteTable.Routes);

AuthConfig.RegisterAuth();

}

  1. Let’s add AccountController into Controller folder and add Login methods. For easy understanding, let’s not add local registration and FormsAuthentication local login process here. We will just add a “Login Using Microsoft” button in the login page similar to below.

  1. Add an action method to redirect user to Microsoft login page on click of the above “Login Using Microsoft” link.

public ActionResult RedirectToMicrosoft()
{
    string provider = "microsoft";
    string returnUrl = "";
    return new ExternalLoginResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl },Request.Url.Scheme ));
}
internal class ExternalLoginResult : ActionResult
{
    public ExternalLoginResult(string provider, string returnUrl)
    {
        Provider = provider;
        ReturnUrl = returnUrl;
    }

    public string Provider { get; private set; }
    public string ReturnUrl { get; private set; }

    public override void ExecuteResult(ControllerContext context)
    {     
        OpenAuth.AuthenticationClients.GetByProviderName(Provider).RequestAuthentication(new HttpContextWrapper(System.Web.HttpContext.Current), new Uri(ReturnUrl));
    }
}
 

 

When user clicks the link, he or she will be redirected to Microsoft login page by specifying “/Account/ExternalLoginCallback” as the redirect url. This url should match the url we saved when creating OAuth application on Microsoft developer console page.

  1. Next, let’s add the landing page or redirect url action method to get back access code and other  details Microsoft is forwarding.

[AllowAnonymous]
public ActionResult ExternalLoginCallback(string returnUrl)
{
    string ProviderName = OpenAuth.GetProviderNameFromCurrentRequest();

    if (ProviderName == null || ProviderName == "")
    {
        NameValueCollection nvs = Request.QueryString;
        if (nvs.Count > 0)
        {
            if (nvs["state"] != null)
            {
                NameValueCollection provideritem = HttpUtility.ParseQueryString(nvs["state"]);
                if (provideritem["__provider__"] != null)
                {
                    ProviderName = provideritem["__provider__"];
                }
            }
        }
    }

    var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }, this.Request.Url.Scheme);
    if (ProviderName == null)
    {
        ProviderName = "microsoft";
    }

    MicrosoftClient msClient = OpenAuth.AuthenticationClients.GetByProviderName(ProviderName) as MicrosoftClient;      
    var authResult = msClient.VerifyAuthentication(new HttpContextWrapper(HttpContext.ApplicationInstance.Context), new Uri(redirectUrl));

    if (!authResult.IsSuccessful)
    {
        return Redirect(Url.Action("Login", "Account"));
    }         

    // User has logged in with provider successfully
    // Check if user is already registered locally
    if (OpenAuth.Login(authResult.Provider, authResult.ProviderUserId, createPersistentCookie: false))
    {
        return Redirect(Url.Action("Index","Home"));
    }
    //Get provider user details
    string ProviderUserId = authResult.ProviderUserId;
    string ProviderUserName = authResult.UserName;
    string FirstName = null;
    string LastName = null;

    string Email = null;
    if (FirstName == null && authResult.ExtraData.ContainsKey("firstname"))
    {
        FirstName = authResult.ExtraData["firstname"];
    }
    if (LastName == null && authResult.ExtraData.ContainsKey("lastname"))
    {
        LastName = authResult.ExtraData["lastname"];
    }
    //Call extension method to get email
    ExtendedMicrosoftClientUserData data = msClient.GetExtraData(authResult.ExtraData["accesstoken"]);
    Email = data.Emails.Preferred;

    if (User.Identity.IsAuthenticated)
    {
        // User is already authenticated, add the external login and redirect to return url
        OpenAuth.AddAccountToExistingUser(ProviderName, ProviderUserId, ProviderUserName, User.Identity.Name);
        return Redirect(Url.Action("Index", "Home"));
    }
    else
    {
        // User is new, save email as username
        string membershipUserName = Email ?? ProviderUserId;
        var createResult = OpenAuth.CreateUser(ProviderName, ProviderUserId, ProviderUserName, membershipUserName);
        
        if (!createResult.IsSuccessful)
        {
            ViewBag.Message = "User cannot be created";
            return View();
        }
        else
        {
            // User created
            if (OpenAuth.Login(ProviderName, ProviderUserId, createPersistentCookie: false))
            {
                return Redirect(Url.Action("Index", "Home"));
            }
        }
    }
    return View();
}
 

On successful login at provider, Microsoft login page will redirect user to application landing page and it passes access code. The landing page will again call Microsoft OAuth API (VerifyAuthentication() method) and get back the access token and basic user details. By default, Microsoft will not forward the email, so I have added an extension method which uses the access token and get additional user details.

Get Email Address from Microsoft OAuth UserInfo Endpoint

public static class MicrosoftClientExtensions
{
    public static ExtendedMicrosoftClientUserData GetExtraData(this MicrosoftClient client, string accessToken)
    {
        ExtendedMicrosoftClientUserData graph;
        var request =
            WebRequest.Create(
                "https://apis.live.net/v5.0/me?access_token=" + EscapeUriDataStringRfc3986(accessToken));
        using (var response = request.GetResponse())
        {
            using (var responseStream = response.GetResponseStream())
            {
                using (StreamReader sr = new StreamReader(responseStream))
                {
                    string data = sr.ReadToEnd();

                    graph = JsonConvert.DeserializeObject<ExtendedMicrosoftClientUserData>(data);
                }
            }
        }

        return graph;
    }
    private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" };
    private static string EscapeUriDataStringRfc3986(string value)
    {
        StringBuilder escaped = new StringBuilder(Uri.EscapeDataString(value));

        // Upgrade the escaping to RFC 3986, if necessary.
        for (int i = 0; i < UriRfc3986CharsToEscape.Length; i++)
        {
            escaped.Replace(UriRfc3986CharsToEscape[i], Uri.HexEscape(UriRfc3986CharsToEscape[i][0]));
        }

        // Return the fully-RFC3986-escaped string.
        return escaped.ToString();
    }
}
public class ExtendedMicrosoftClientUserData
{
    public string FirstName { get; set; }
    public string Gender { get; set; }
    public string Id { get; set; }
    public string LastName { get; set; }
    public Uri Link { get; set; }
    public string Name { get; set; }
    public Emails Emails { get; set; }
}
public class Emails
{
    public string Preferred { get; set; }
    public string Account { get; set; }
    public string Personal { get; set; }
    public string Business { get; set; }
}
 

Thanks to the user amp in SO for the above solution here. You can also use the MicorosoftScopedClient as an alternate to MicrosoftClient for OAuth.

  1. Finally, we will add LogOff action to logoff user.

public ActionResult LogOff()

{

FormsAuthentication.SignOut();

return Redirect(Url.Action("Index","Home"));

}

  1. That’s it. Press F5.

Click Login Using Micorost Link. This will redirect to Microsoft Login page. Enter your hotmail credentials and click “Allow access” button to allow Microsoft to pass user profile info to the application redirect url. Refer below,

Download the source and see it in action. I have also included WebForms version of the project for reference. The WebForms version will use the default project “Individual User Accounts” template code for Microsoft integration.

 



Feedback

Comments