Azure Mobile Services (AMS) is a great platform for .NET developers to build complex apps that require a backend in almost no time and at very competitive prices. I’ve been using it for about 6 months to develop a beer tracking app called BeerDrinkin.
One of the requirements of BeerDrinkin was its ability to sync account data accross all the users devices and AMS makes this incredibly easy with offline sync.
I couldn’t help but feel that if I’m going to be storing data in Azure, showing what beers people love and hate, then I really should be doing something more interesting than simply syncing to a handful of devices. This is why I decided to try and build a beer recommendation engine into BeerDrinkins backend. The aim is to make use of Azure Machine Learning to make suggestions about what beers you might like based on your constumption history and those of your peers.
In order to build a users profile to feed into machine learning, I needed more information than the simple GUID that AMS returns when calling the FacebookLoginProvier within the ConfigOptions of a Mobile Service WebApiConfig class.
The information that I wanted to have about the user:
- Email Address
- First Name
- Last Name
- Gender
- Date of Birth
I will be adding additional data about users at various points during the users interaction with the app. One example is location data and I even plan on recroding local weather conditions for each beer the user checks in. With this, I can use machine learning to predict beers based on current weather conditions. This is important as on a warm day, most people will likely want a lager rather than a thick, blood warming Belgian double.
Creating a custom LoginProvider
To fetch the extra information, I needed to do a couple of things. First things first, I needed to remove the default FacebookLoginProvider from my ConfigOptions. To do this I called the following:
options.LoginProviders.Remove(typeof(FacebookLoginProvider));
I then went ahead and created a new class which I named CustomFacebookLoginProvider which importantly overrides the CreateCentials method.
public class CustomFacebookLoginProvider : FacebookLoginProvider{ public CustomFacebookLoginProvider(HttpConfiguration config, IServiceTokenHandler tokenHandler) : base(config, tokenHandler) { } public override ProviderCredentials CreateCredentials(ClaimsIdentity claimsIdentity) { var accessToken = string.Empty; var emailAddress = string.Empty; foreach (var claim in claimsIdentity.Claims) { if (claim.Type == "Zumo:ProviderAccessToken") { accessToken = claim.Value; } if (claim.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress") { emailAddress = claim.Value; } } }}
I now had some basic information regarding the user, such as their Facebook token (which is essential for gathering more information) and their email address. I then use a 3rd party Nuget package to query Facebook’s opengraph for more information regarding the user.
The entire method in BeerDrinkin’s Azure backend looks something like this:
public class CustomFacebookLoginProvider : FacebookLoginProvider{ public CustomFacebookLoginProvider(HttpConfiguration config, IServiceTokenHandler tokenHandler) : base(config, tokenHandler) { } public override ProviderCredentials CreateCredentials(ClaimsIdentity claimsIdentity) { var accessToken = string.Empty; var emailAddress = string.Empty; foreach (var claim in claimsIdentity.Claims) { if (claim.Type == "Zumo:ProviderAccessToken") { accessToken = claim.Value; } if (claim.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddres") { emailAddress = claim.Value; } } if (string.IsNullOrEmpty(accessToken)) return null; var client = new FacebookClient(accessToken); dynamic user = client.Get("me"); DateTime dateOfBirth; DateTime.TryParse(user.birthday, out dateOfBirth); //Keeping userItem for the moment but may well kill it. I was going to seperate userItem into public info and accountItem into public var userItem = new UserItem { Id = user.id, }; var accountItem = new AccountItem { Id = userItem.Id, Email = emailAddress, FirstName = user.first_name, LastName = user.last_name, IsMale = user.gender == "male", DateOfBirth = dateOfBirth, AvatarUrl = $"https://graph.facebook.com/{userItem.Id}/picture?type=large" }; var context = new BeerDrinkinContext(); if (context.UserItems.FirstOrDefault(x => x.Id == userItem.Id) != null) return base.CreateCredentials(claimsIdentity); context.AccountItems.Add(accountItem); context.UserItems.Add(userItem); context.SaveChanges(); return base.CreateCredentials(claimsIdentity); }}
Using the CustomFacebookLoginProvider
In order to use my implementation of the login provider, I needed to go ahead and add it to the ConfigOptions.
options.LoginProviders.Add(typeof(CustomFacebookLoginProvider));
Scopes
One thing I forgot when I first implemented this approach was ensuring that I updated my scopes. By default, Facebook wont provide me with the information I require. In order to have Azure Mobile Service pass the correct request to Facebook, I needed to log into my Azure management portal and add MS_FacebookScope to my App Settings. The exact scope I’m requesting is email user_birthday user_friends.
Disclaimer
BeerDrinkin (espically the backend) is worked on mostly whilst ‘testing’ the app (drinking beer). Some of the code is horrible and need to be refactored. The above code could 100% do with a tidy up but works so I’ve left it as is. The project is on GitHub.so please do contribute if you can.