Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#

OWIN OAuth2 Authentication for Facebook and Google

Rate me:
Please Sign up or sign in to vote.
4.65/5 (11 votes)
23 Oct 2018CPOL2 min read 20.8K   22   3
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.

Image 1

Using the Code

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

C#
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.

C#
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.

C#
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.

C#
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, GenericView<User> user = null)
        {
            switch(type)
            {
                case SignInApplication.Cookie:
                       Create(user);
                    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;
            }
        }

        // normal login
        public void Create(GenericView<User> model)
        {

            var user = _dbContext.Get<User>().Where(x => x.Email.Contains(model.View.Email) &&
                        x.Password == model.View.Password).LoadChildren().ExecuteFirstOrDefault();
            if (user == null)
                return;
            Authorize(user, model.View.RememberMe);
        }


        // 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, bool isPersistent = true)
        {
            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 = isPersistent }, 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.

C#
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.

C#
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
    {
        [AllowAnonymous]
        public ActionResult Index(GenericView<User> user, string type = "")
        {
            if (Request.IsAuthenticated)
                return Redirect("~/Home");
            else if (type.ConvertValue<SignInApplication?>().HasValue)
            {
                using (var manager = new UserManager())
                {
                    if (!Request.IsAuthenticated)
                    {
                        manager.SignIn(type.ConvertValue<SignInApplication>(), user);
                    }
                }

                if (type.ConvertValue<SignInApplication>() == SignInApplication.Cookie &&
                    !Request.IsAuthenticated)
                    return View(user.Error("Email or Password could not be found."));
            }
            return View(user ?? new GenericView<User>());
        }

        #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
    }
}

if you have more then one Role you may want to specify which role has access to which controller. In that case you will need to create and inherit from AuthorizeAttribute 

C#
    using System.Linq;
using System.Security.Claims;
using System.Web;
using System.Web.Mvc;

namespace IProduct.Models
{
    public class PAuthorize : AuthorizeAttribute
    {
        public PAuthorize()
        {
            base.Roles = IProduct.Modules.Roles.Customers.ToString();
        }

        public PAuthorize(params IProduct.Modules.Roles[] roles)
        {
            if (roles != null && roles.Any())
                base.Roles = string.Join(",", roles.Select(x => x.ToString()));
            else
                base.Roles = IProduct.Modules.Roles.Customers.ToString();
        }

        protected override void HandleUnauthorizedRequest(AuthorizationContext ctx)
        {
            if (!ctx.HttpContext.User.Identity.IsAuthenticated)
                base.HandleUnauthorizedRequest(ctx);
            else
            {
                var role = HttpContext.Current.GetOwinContext().Authentication.User.Claims
                           .FirstOrDefault(x => x.Type == "role" || x.Type == ClaimTypes.Role)?.Value;
                 // Role check IsAuthenticated
                if (!(role == IProduct.Modules.Roles.Administrator.ToString() || Roles.Contains(role))) 
                {
                    ctx.Result = new ViewResult { ViewName = "Unauthorized" };
                    ctx.HttpContext.Response.StatusCode = 403;
                }
            }
        }
    }
}

 

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

HTML
@model IProduct.Modules.Library.Custom.GenericView<IProduct.Modules.Library.User>
@{
    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>

    @using (Html.BeginForm("Index", "Account",
     new { type = IProduct.Modules.SignInApplication.Cookie.ToString() }, FormMethod.Post, null))
    {
        <div class=" view">
            <div class="viewData">
                <div class="form-group">
                    @Html.TextBoxFor(x => x.View.Email,
                    new { @class = "form-control", placeholder = "Email", id="email-sign-in" })
                </div>

                <div class="form-group">
                    @Html.PasswordFor(x => x.View.Password,
                    new { @class = "form-control", placeholder = "Password", id = "password-sign-in" })
                </div>

                <div class="form-group">
                    @Html.CheckBoxFor(x => x.View.RememberMe,
                    new { label = "Remember Me", boxClass = "float-left" })
                    <a class="float-right">Forgot Password?</a>
                </div>
                <div class="form-group">
                    <button type="submit" class="m-button loginBtn--signin">
                        Sign In
                    </button>

                    <button class="m-button loginBtn--signup">
                        Sign Up
                    </button>
                </div>

            </div>
        </div>
    }
    @Html.Action("SignUp", new { user = "" })
    @{
        if (!Model.Success && Model.GetHtmlError() != null)
        {
            <div class="error">
                @Html.Raw(Model.GetHtmlError())
            </div>
        }
    }
</div>

<script>
    var signupDialog = $("body").dialog(
        {
            data: $(".signupForm"),
            title: "Sign Up",
            removable: false
        });

    $(".loginBtn--signup").click(function ()
    {
        signupDialog.Show();
        return false;
    });

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

    $(document).ready(function ()
    {
        $(".login input[type=checkbox]").checkBox();
        $(window).resize(function ()
        {
            $(".login").center();
        });
        $(".login").center();
        toLocation($(".m-button"));
    });
</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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Sweden Sweden
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionOpenID and Auth2.0 Pin
Aydin Homay7-Jan-19 5:25
Aydin Homay7-Jan-19 5:25 
Questionuse OpenID Connect not Oauth / OWIN Pin
Figuerres16-Oct-18 9:13
Figuerres16-Oct-18 9:13 
AnswerRe: use OpenID Connect not Oauth / OWIN Pin
Alen Toma16-Oct-18 15:51
Alen Toma16-Oct-18 15:51 
sound interesting thank you.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.