Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

OWIN OAuth2 Authentication for Facebook and Google

0.00/5 (No votes)
15 Oct 2018 1  
OWIN OAuth2 authentication for Facebook and Google without Entityframework

Introduction

How many of us struggle very much to work with OWIN and implement Social networks login?

Code

You could have a look at a project I'm currently developing (IProject), it contains all the code in this article.

Background

I have been working for the past three days to learn OWIN without Entityframework work, at first it felt impossible and I began to give up.

But when I got things working, I noticed that it was really and truly simple.

I'm going to explain to you a very simple and organized way to setup Google, Facebook and Cookies authentication with OWIN.

As an ORM I'm using EntityWorker.Core, you could use any other ORM. It really does not matter.

Using the Code

We begin with which package you will need for this article:

Microsoft.Owin.Host.SystemWeb 
Microsoft.Owin.Security.Facebook
Microsoft.Owin.Security.Google
Microsoft.Owin.Security.Cookies

Create two classes, Startup.cs which will be located at the root of your application and Startup.Auth.cs which will be located under app_Start and contain OWIN configurations.

using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(IProduct.Startup))]
namespace IProduct
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            ConfigureAuth(app);
        }
    }
}

And Startup.Auth.cs.

For Google and Facebook apps to work, you will have to create clientId and appId for them.

For Google, you could learn how to here.

For Facebook you could learn how to here.

using IProduct.Models.OAuthProviders;
using IProduct.Modules;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Google;
using Microsoft.Owin.Security.Facebook;
using Owin;
using System;

namespace IProduct
{
    public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app)
        {
            var googleCredentials = Actions.LoadCredentials(SignInApplication.Google);
            var facebookCredentials = Actions.LoadCredentials(SignInApplication.Facebook);

            if(googleCredentials == null)
               throw new Exception
                   ("GoogleCredentials could not be found(GoogleOAuth2Authentication)");

            if(facebookCredentials == null)
               throw new Exception
                   ("FacebookCredentials could not be found(FacebookAuthenticationOptions)");


            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
            var cookieOptions = new CookieAuthenticationOptions
            {
                LoginPath = new PathString("/Account/Index"),
                SlidingExpiration = true,
                Provider = new CookieProvider(),
                ExpireTimeSpan = TimeSpan.FromDays(7)
            };
            app.UseCookieAuthentication(cookieOptions);

            var googleOption = new GoogleOAuth2AuthenticationOptions()
            {
                ClientId = googleCredentials.Client_Id,
                ClientSecret = googleCredentials.Client_Secret,
                CallbackPath = new PathString("/Google"),
                Provider = new GoogleProvider(),
                AuthenticationType = googleCredentials.Provider
                //SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie
            };
            app.UseGoogleAuthentication(googleOption);

            var facebookOptions = new FacebookAuthenticationOptions()
            {
                AppSecret = facebookCredentials.Client_Secret,
                AppId = facebookCredentials.Client_Id,
                AuthenticationType = facebookCredentials.Provider,
                Provider = new FacebookProvider()                
            };
            app.UseFacebookAuthentication(facebookOptions);
        }
    }
}

Let's now create a UserManager.cs which will contain logic for ClaimsIdentity and other operation.

using IProduct.Modules;
using IProduct.Modules.Data;
using IProduct.Modules.Library;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Facebook;
using Microsoft.Owin.Security.Google;
using System;
using System.Linq;
using System.Security.Claims;
using System.Web;

namespace IProduct.Models
{
    public class UserManager : IDisposable
    {
        private DbContext _dbContext = new DbContext();

        public void SignIn(SignInApplication type)
        {
            switch(type)
            {
                case SignInApplication.Cookie:
                    break;

                case SignInApplication.Facebook:
                    HttpContext.Current.GetOwinContext().Authentication.Challenge(
                    new AuthenticationProperties {  IsPersistent = true,

                    RedirectUri = "Account/Facebook" },
                    SignInApplication.Facebook.ToString());
                    break;

                case SignInApplication.Google:
                    HttpContext.Current.GetOwinContext().Authentication.Challenge(
                    new AuthenticationProperties { IsPersistent = true,

                    RedirectUri = "Account/Google" },
                    SignInApplication.Google.ToString());
                    break;
            }
        }

        // For Facebook
        public void Create(FacebookAuthenticatedContext context)
        {

            if(string.IsNullOrEmpty(context.Email))
                return;
            var email = context.Email;
            var user = _dbContext.Get<user>().Where(x => x.Email == email).ExecuteFirstOrDefault();
            if(user == null) // if User dose not exist in our database then create it 
            {
                user = new User
                {
                    Email = email,
                    Password = "xxxxxxx", // User have to change it later
                    Person = new Person()
                    {
                        FirstName = context.Name,
                        LastName = "",
                        Address = new Address()
                        {
                            AddressLine = string.Empty,
                            Country_Id = _dbContext.Get<country>()

                                        .Where(x => x.CountryCode.Contains("sv-se"))
                                        .ExecuteFirstOrDefault().Id.Value
                        }
                    },
                    Role = _dbContext.Get<role>()

                           .Where(x => x.RoleType == Roles.Customers)
                           .ExecuteFirstOrDefault()

                };
                _dbContext.Save(user).SaveChanges();
                Authorize(user);
            }
        }

        // For Google
        public void Create(GoogleOAuth2AuthenticatedContext context)
        {
            if(string.IsNullOrEmpty(context.Email))
                return;
            var email = context.Email;
            var user = _dbContext.Get<user>().Where(x => x.Email == email).ExecuteFirstOrDefault();
            if(user == null) // if the user dose not exist in our database then create it 
            {
                user = new User
                {
                    Email = email,
                    Password = "xxxxxxx", // User have to change it later
                    Person = new Person()
                    {
                        FirstName = context.Name,
                        LastName = context.FamilyName,
                        Address = new Address()
                        {
                            AddressLine = string.Empty,
                            Country_Id = _dbContext.Get<country>()

                            .Where(x => x.CountryCode.Contains("sv-se"))

                            .ExecuteFirstOrDefault().Id.Value
                        }
                    },
                    Role = _dbContext.Get<role>()

                           .Where(x => x.RoleType == Roles.Customers).ExecuteFirstOrDefault()

                };
                _dbContext.Save(user).SaveChanges();
                Authorize(user);
            }
        }

        private void Authorize(User user)
        {
            var ident = new ClaimsIdentity(new[] {
              new Claim(ClaimTypes.NameIdentifier, user.Email),
              new Claim
               ("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",
             "ASP.NET Identity",
             "http://www.w3.org/2001/XMLSchema#string"),
              new Claim(ClaimTypes.Name, user.Person.FullName),
              new Claim(ClaimTypes.Email, user.Email),
              new Claim(ClaimTypes.Role, user.Role.Name)
            }, CookieAuthenticationDefaults.AuthenticationType);
            /// write to Cookie
            HttpContext.Current.GetOwinContext()
           .Authentication.SignIn(new AuthenticationProperties { IsPersistent = true }, ident);
        }

        public void SignOut()
        {
            HttpContext.Current.GetOwinContext()
           .Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
        }

        public User GetCurrentUser()
        {
            var email = HttpContext.Current.GetOwinContext()
                       .Authentication.User.Claims.

                        FirstOrDefault(x => x.Type == "email" || x.Type == ClaimTypes.Email)?.Value;
           
            if(string.IsNullOrEmpty(email))
                return null;

            var user = _dbContext.Get<user>()

            .Where(x => x.Email == email).LoadChildren()

            .IgnoreChildren(x => x.Invoices).ExecuteFirstOrDefault();
            return user;
        }

        public void Dispose()
        {
            _dbContext.Dispose();
        }
    }
}

Now that we created the UserManager, we will need to create two other classes. Those are the providers, FacebookProvider and GoogleProvider.

Sadly, Owin does not register the ClaimTypes automatically, we will need to take the information from those two providers present and add them to OWIN by ourselves.

Or you could add a delegate to OnAuthenticated in Startup.Auth class if you would like instead, I chose this way for future code adding.

using Microsoft.Owin.Security.Google;
using System.Threading.Tasks;

namespace IProduct.Models.OAuthProviders
{
    public class GoogleProvider : GoogleOAuth2AuthenticationProvider
    {
        public override Task Authenticated(GoogleOAuth2AuthenticatedContext context)
        {
            using(var m = new UserManager())
                m.Create(context);
            return base.Authenticated(context);
        }
    }
}

using Microsoft.Owin.Security.Facebook;
using System.Threading.Tasks;

namespace IProduct.Models.OAuthProviders
{
    public class FacebookProvider : FacebookAuthenticationProvider
    {
        public override Task Authenticated(FacebookAuthenticatedContext context)
        {
            using(var m = new UserManager())
                m.Create(context);
            return base.Authenticated(context);
        }
    }
}

Well that's all we needed for OWIN implementation. now let's see how to invoke calling those providers by the controller.

I will create an AccountController that will contain two methods for the Facebook and Google callback and also a method for signin.

using EntityWorker.Core.Helper;
using IProduct.Controllers.Shared;
using IProduct.Models;
using IProduct.Modules;
using System.Web.Mvc;

namespace IProduct.Controllers
{
    public class AccountController : SharedController
    {
        public ActionResult Index()
        {
            if(Request.IsAuthenticated)
               return Redirect("~/Home");
            return View();
        }

        [AllowAnonymous]
        public void SignIn(string type = "")
        {
            using(var manager = new UserManager())
            {
                if(!Request.IsAuthenticated)
                {
                   // convert type to enum (ConvertValue exist in EntityWorker.Core)
                    manager.SignIn(type.ConvertValue<signinapplication>()); 
                }
            }
        }

        #region Google
        // we may need to add some changes here later as if now, the Google provider
        // take care of the login
        [AllowAnonymous]
        public ActionResult Google(string error)
        {
            if(Request.IsAuthenticated)
                return Redirect("~/Home");

            return Redirect("Index");
        }
        #endregion

        #region Facebook
        // we may need to add some changes here later as if now, the Facebook provider
        // take care of the login
        [AllowAnonymous]
        public ActionResult Facebook(string error)
        {
            if(Request.IsAuthenticated)
                return Redirect("~/Home");

            return Redirect("Index");
        }
        #endregion
    }
}

And now all that's left is the HTML and a little Jquery.

@{
    Layout = "";
}

@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
@Scripts.Render("~/bundles/jquery")

<div class="login">
    <div class="externalLogin view">
        <div class="viewData">
            <button href='@Url.Action("SignIn", new {type= "Facebook"})' 

                            class="loginBtn loginBtn--facebook">
                Login with Facebook
            </button>

            <button href='@Url.Action
                 ("SignIn", new {type= "Google"})' class="loginBtn loginBtn--google">
                Login with Google
            </button>
        </div>
    </div>

    <div class="seperator">
        <div class="text">OR</div>
    </div>

    <div class="internallogin view">
        <div class="viewData">
            <div class="form-group">
                <input type="text" class="form-control" placeholder="Username" />
            </div>
            <div class="form-group">
                <input type="password" class="form-control" placeholder="Username" />
            </div>

            <button class="loginBtn loginBtn--signin">
                Sign In
            </button>
        </div>
    </div>
</div>

<script>
    /// make a button act like a link
    function toLocation(items)
    {
        $(items).each(function ()
        {
            var location = $(this).attr("href");
            if(location && location !== "")
                $(this).click(function ()
                {
                    window.location.href = location;
                });
        });
    }

    $(document).ready(function ()
    {
        $(window).resize(function ()
        {
            $(".login").center();
        });
        $(".login").center();
        toLocation($(".loginBtn"));
    });
</script>

Points of Interest

I really hope this helps people, and lastly, thank you for reading my code.

History

  • 15th October, 2018: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here