Introduction
If you ever wondered how social publishing companies like Hootsuite or KontentKloud manage to save user's Facebook login data and later post messages to Facebook on behalf of that user, this article has the answers. You can also download the complete project discussed here.
Background
Latest updates to Microsoft Visual Studio made it very easy to create a website which enables users to log in with external login provider, like Facebook. All you need to do is to get app keys from Facebook developer site. Unfortunately, the default Facebook Authentication Provider supplied by Microsoft does not allow saving user's authentication token for later reuse.
In this article, I show how to create a very simple authentication provider that does exactly that, allowing you to save Facebook authentication token to a database, and later log in and upload pictures to Facebook impersonating another user.
Using the Code
Here are the 5 steps to build the example web site:
- Create default MVC website
- Add custom authentication provider which extracts user token
- Add token field to user profile where the token will be saved
- Customize Account controller to actually save token into the user profile during registration
- Add code that uses persisted token when uploading picture to Facebook
It might sound complicated but in fact it takes just several lines of code to accomplish all this.
Step 1
Open Visual Studio 2013 and create default MVC website using Individual User Accounts for authentication.
Call the project PersistentFacebookAuth
.
Step 2
Add new class to the project, call it MyFacebookAuthenticationProvider.cs. Add this code to the class:
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Owin.Security.Facebook;
namespace PersistentFacebookAuth
{
public class MyFacebookAuthenticationProvider : FacebookAuthenticationProvider
{
public const string MyTokenClaimName = "my:FacebookToken";
public override Task Authenticated(FacebookAuthenticatedContext context)
{
context.Identity.AddClaim(new Claim(MyTokenClaimName, context.AccessToken));
return base.Authenticated(context);
}
}
}
This code extends the default FacebookAuthenticationProvider
with one line of code, which takes AccessToken
from authentication context and saves it in the Claims
collection. Later, in the Account controller, we will find the token in the Claims
collection using the claim name "my:FacebookToken
". This is an arbitrary name, you can come up with your own, it just must be unique in the collection - for convenience, we store it in the MyTokenClaimName
constant.
We are now ready to configure our website for Facebook login utilizing new authentication provider.
In the App_Start folder, find Startup.Auth.cs file and open it. Find this place in the code:
If you uncomment those lines, the default provider will be used. We don't need that. Instead, we will add code that directs the app to use our custom provider:
var fbOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions
{
SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie,
AppId = "--your id here --",
AppSecret = "-- your key here --",
Provider = new MyFacebookAuthenticationProvider()
};
fbOptions.Scope.Add("email");
fbOptions.Scope.Add("read_stream");
fbOptions.Scope.Add("publish_stream");
app.UseFacebookAuthentication(fbOptions);
Don’t forget to copy AppId
and AppSecret
keys from Facebook developer site. (Simple description of the procedure can be found here).
You can also change the Scope
collection - it defines the permissions your app will request from the user when authenticating.
Step 3
Now, we need a place to store the authentication token. Any place that can hold long string
s will do. In this example, we add another column to the AspNetUsers
database table holding user profile. You really don't need to know much about that database. Just add one line of code and the default backend in the project will do the rest.
In the Models folder, find file IdentityModels.cs and open it. Add the FacebookToken
field declaration to the ApplicationUser
class:
public string FacebookToken { get; set; }
The ApplicationUser
class should now look like this:
public class ApplicationUser : IdentityUser
{
public string FacebookToken { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
var userIdentity = await manager.CreateIdentityAsync
(this, DefaultAuthenticationTypes.ApplicationCookie);
return userIdentity;
}
}
On the first run of your website, the database will be generated and the FacebookToken
column will be added to the AspNetUsers
table.
Step 4
Now, we need to modify the Account controller to actually save token into the user profile during the registration process. This happens after the user authenticates with Facebook and confirms the registration.
In the Controllers folder, find and open the AccountController.cs file. Find the ExternalLoginConfirmation
function:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model,
string returnUrl)
Inside the function, find the line where new ApplicationUser
is constructed:
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
Here, we will add our authentication token which was already extracted by our custom authentication provider. Add a new line of code above the ApplicationUser
constructor:
var tokenClaim = info.ExternalIdentity.Claims.FirstOrDefault
(c => c.Type == MyFacebookAuthenticationProvider.MyTokenClaimName);
Here, we search through the Claims
collection to find our custom claim holding the authentication token.
Now, modify the ApplicationUser
constructor to add the FacebookToken
value:
var user = new ApplicationUser
{ UserName = model.Email, Email = model.Email, FacebookToken = tokenClaim.Value };
The resulting code fragment, saving token with the user profile, should look like this:
var tokenClaim = info.ExternalIdentity.Claims.FirstOrDefault
(c => c.Type == MyFacebookAuthenticationProvider.MyTokenClaimName);
var user = new ApplicationUser
{ UserName = model.Email, Email = model.Email, FacebookToken = tokenClaim.Value };
var result = await UserManager.CreateAsync(user);
This completes the part which extracts and saves authentication token of a Facebook user.
In the next step, we will change default Home page view and controller to demonstrate how to use the token when uploading pictures to the user's Facebook account without requiring user to log in.
Step 5 - Upload Example
The goal of this step is to modify Home page to display a File Upload button with the list of registered users, allowing you to upload a picture to the Facebook profile of selected user.
To save you some time, the code utilizes Facebook library which you can get from NuGet. Right-click on the project name, select "Manage NuGet Packages..." option, and enter "facebook
" into the search box:
Find the Facebook
package and click Install. You are now ready to complete our project.
In the Controllers folder, find and open the HomeController.cs file.
First thing we want to do here is to display the list of currently registered users who have Facebook login information. Modify Index
function like this:
public ActionResult Index()
{
var users = HttpContext.GetOwinContext().GetUserManager
<ApplicationUserManager>().Users.Where(u => u.FacebookToken != null).ToList();
if (users != null && users.Count > 0) ViewBag.Users = users;
return View();
}
Here, we put the list of users into the ViewBag
. Now let's modify the Home Index
View. In the Views/Home folder, find Index.cshtml file and open it.
Add this HTML code to any place you like:
@if (ViewBag.Users != null)
{
<div class="col-md-8">
@using (Html.BeginForm("Upload", "Home",
FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="form-horizontal">
<div class="form-group">
<label class="col-md-6 control-label">Select picture to upload:</label>
<div class="col-md-6">
<input type="file" name="file" />
</div>
</div>
<div class="form-group">
<label class="col-md-7 control-label">Choose Facebook account to upload
</label>
</div>
@foreach (PersistentFacebookAuth.Models.ApplicationUser user in ViewBag.Users)
{
<div class="form-group">
<label class="col-md-6 control-label">@user.Email</label>
<div class="col-md-6">
<button class="btn btn-success" id="userID"
name="userID" value="@user.Id">Upload</button>
</div>
</div>
}
</div>
}
</div>
}
What is essential here is the HTML Form, input type="file
", and foreach
statement listing registered users' emails with corresponding submit button. When you click a button, the Upload
action of the home controller will be called with the corresponding userID
value.
Now let's create that Upload
action function which will do all the uploading work.
Open again the HomeController.cs file. Add this code there:
[HttpPost]
public async Task<ActionResult> Upload(string userID)
{
if (Request.Files.Count > 0)
{
var selectedUser = await HttpContext.GetOwinContext().GetUserManager
<ApplicationUserManager>().FindByIdAsync(userID);
var facebookUserID = selectedUser.Logins.Where
(u => u.LoginProvider == "Facebook").Select(u => u.ProviderKey).FirstOrDefault();
var facebookUserToken = selectedUser.FacebookToken;
var imgFile = Request.Files[0];
if (imgFile != null && imgFile.ContentLength > 0)
{
var pictureBytes = new byte[imgFile.ContentLength];
imgFile.InputStream.Read(pictureBytes, 0, imgFile.ContentLength);
await uploadToFacebook(facebookUserID,
facebookUserToken, pictureBytes, Path.GetFileName(imgFile.FileName));
return Redirect("<a abp="9652"
href="https: }
}
return RedirectToAction("Index");
}
First, we take supplied userID
and retrieve selectedUser
data from the profile database table. For that user, we populate facebookUserID
and facebookUserToken
variables - we will use these values when uploading picture to Facebook.
After that, we retrieve picture file name and image bytes array from the Request, and send all the data to the uploadToFacebook
function.
Below is the code for the uploadToFacebook
function, which actually uploads the picture to Facebook impersonating the selected user.
private async Task<bool> uploadToFacebook(string facebookUserID,
string token, byte[] pictureBytes, string fileName)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
var mediaObj = new FacebookMediaObject
{
FileName = fileName,
ContentType = "image/" + Path.GetExtension(fileName).Substring(1),
};
mediaObj.SetValue(pictureBytes);
var args = new Dictionary<string, object>();
args.Add("source", mediaObj);
var client = new FacebookClient(token);
await client.PostTaskAsync(string.Format("/{0}/photos", facebookUserID), args);
return true;
}
In the first line here, we tell .NET library to use TLS security protocol - Facebook stopped responding to default SSL3 requests.
Then, we pack image bytes into the FacebookMediaObject
, and add that object to the arguments collection.
Now, we get to the place where we use authentication token in the FacebookClient
constructor. This is actually the point of the whole exercise - to impersonate a Facebook user using the token saved during registration procedure.
Finally, we submit the upload request to Facebook and redirect requestor to the user's image library.
That's it. You now have a working website that allows you to upload pictures to another user's Facebook album.
Download the source code to see all the code and try it.
Remember, this is an example, not a production quality website. Besides, you should never allow strangers to post to other users Facebook profile (see license and other documents on the Facebook site).
Points of Interest
Similar code can be used to connect to Twitter or LinkedIn. If you'd like to see how that can be done leave a comment here and I'll post another article with more details.
You can see how this code works in real production system here: https://my.kontentkloud.com/account/register/010basic
I am planning another article describing how to connect your website to Microsoft Office 365 business cloud. Leave me a comment if you have a specific question on that topic.