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
};
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;
}
}
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)
{
user = new User
{
Email = email,
Password = "xxxxxxx",
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);
}
}
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)
{
user = new User
{
Email = email,
Password = "xxxxxxx",
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);
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)
{
manager.SignIn(type.ConvertValue<signinapplication>());
}
}
}
#region Google
[AllowAnonymous]
public ActionResult Google(string error)
{
if(Request.IsAuthenticated)
return Redirect("~/Home");
return Redirect("Index");
}
#endregion
#region Facebook
[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>
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