Claims based authentication on Windows Azure
Claims based authentication can greatly improve user’s experience by allowing them to login to our site using external trusted providers such as Google, Yahoo etc. In this article I will show you how to integrate claims based authentication with existing membership provider. This is common scenario if you don’t have any on premises applications using Active Directory needing to connect to the cloud. If this is the case we would have to use WIF (Windows Identity Foundation) authentication modules and Active Directory Federation Services installed on premises.
In our simple example we will use DotNetOpenAuth to perform claims based authentication along side default membership provider. In order to successfully authenticate the user we will have to correlate user data stored in membership table with the UserID received from external claims callback, in our case we will receive email address as a user-name from Google.
Following diagram presents high level overview of our solution:
How it works:
User can login to our site as normal using standard login button. If he wishes to login using eg. Google account he will have to click the button below saying ‘Log in with Google’.
After clicking that button, user is redirected to Google’s login page. If the Google login is successful then user is redirected back to our site. Our generic handler validates the claims stored in the response context. If the validation is successful, we are getting email address that we will use to login the user. Before doing that we need to first find the user in our database by email address and get his data.
Let’s start implementation.
In the Global.asax file we need to attach UserAuthenticated event. We will use it to get callback response from Google containing claimed userID.
public void RegisterEvents() { //attach login event from the generic handler GoogleLoginHandler.UserAuthenticated += new EventHandler<EventArgs<string>>(GoogleLoginHandler_UserAuthenticated); } void GoogleLoginHandler_UserAuthenticated(object sender, EventArgs<string> e) { var accountController = new Controllers.AccountController(); accountController.LogOnUsingOpenId(e.Data); //pass in email address of the user to be logged in } protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); RegisterEvents(); }
Next, lets add following handler GoogleLoginHandler.ashx to our project. In that handler we will redirect to Google login page and read the response after login is successful. See in-line comments:
public void ProcessRequest(HttpContext context) { using (var openid = new OpenIdRelyingParty()) { openid.Behaviors.Add(new AXFetchAsSregTransform()); var response = openid.GetResponse(); if (response == null) { // Check if already logged in var request = openid.CreateRequest(GoogleOPIdentifier); request.AddExtension(new UIRequest { Mode = UIModeDetectSession }); request.Mode = AuthenticationRequestMode.Immediate; request.AddExtension(new ClaimsRequest { Email = DemandLevel.Require }); request.RedirectToProvider(); } else { if (response.Status == AuthenticationStatus.Authenticated) { var claims = response.GetExtension<ClaimsResponse>(); var userEmail = claims.Email; if (UserAuthenticated != null) { //Log user in UserAuthenticated(this, new EventArgs<string>(userEmail)); } context.Response.Redirect(context.Request["ReturnUrl"]); } else if (response.Status == AuthenticationStatus.SetupRequired) { var request = openid.CreateRequest(GoogleOPIdentifier); request.AddExtension(new ClaimsRequest { Email = DemandLevel.Require }); request.RedirectToProvider(); } else { context.Response.Redirect(context.Request["ReturnUrl"]); } } } }
We will use following function to redirect to login page and back to destination by reading the ReturnUrl param:
public static void TryToLogin(string defaultRedirection = "/Home/Index") { //extract return url if exists var returnUrl = HttpContext.Current.Request.UrlReferrer != null && HttpContext.Current.Request.UrlReferrer.ToString().Contains("ReturnUrl=") ? HttpContext.Current.Request.UrlReferrer.ToString().Split(new string[] { "ReturnUrl=" }, StringSplitOptions.None)[1] : defaultRedirection; if (returnUrl.Trim().Length == 0) { returnUrl = defaultRedirection; } //enforce default if empty HttpContext.Current.Response.Redirect("~/GoogleLoginHandler.ashx?ReturnUrl=" + returnUrl); }
Our generic handler and declarations will look as follows:
public class GoogleLoginHandler : IHttpHandler { public static event EventHandler<EventArgs<string>> UserAuthenticated; private const string GoogleOPIdentifier = "https://www.google.com/accounts/o8/id"; private const string UIModeDetectSession = "x-has-session";
We will also extend EventArgs class to accept generic param that we will use in our callback handler.
namespace System { public class EventArgs<T> : EventArgs { public EventArgs() { } public EventArgs(T data) { this.Data = data; } public T Data { get; set; } } }
And finally we call following function to authenticate user containing verified email address that matches email address in our membership table.
/// <summary> /// Private login method for openId /// </summary> /// <param name="emailAddress"></param> /// <returns></returns> [Authorize] internal ActionResult LogOnUsingOpenId(string emailAddress) { var userName = Membership.GetUserNameByEmail(emailAddress); if (userName != null) { FormsAuthentication.SetAuthCookie(userName, true); } return View(); }
I have included project files below so you can see it working and adjust it to your requirements 🙂