Microsoft.AspNet.Mvc.Facebook.Controllers approach

Topics: ASP.NET MVC, General
Oct 23, 2012 at 8:37 AM

Hi

I note with interest the addition of Microsoft.AspNet.Mvc.Facebook.Controllers.

I have not had time to use this recently checked in code, but wanted to share some of the directions that I typically use for Facebook applications (Tab Applications, Canvas Applications, Facebook Connect sites). For these scenarios I have built common code which I use for multiple Facebook apps per month.

- auth always via Facebook Javascript SDK modal Auth Dialog, requiring callback to server with only auth token to create new user

- create new user using an extended IMembershipProvider interface that handles login from external provider

- typically require custom scope/claims

- automatically log in via Forms Authentication returning user of Canvas Application is user id present in Signed Request ie. never save Signed Request as a hidden field

- quick access to page.liked property on first request if Signed Request for Like Gates on a Tab Application

- quick access to user.locale or user.country on first request from Signed Request to correctly apply localization in ASP.NET 

- light weight System.Net.Http.HttpClient async proxy for Graph API; full proxy classes for Graph API objects rather than dynamic object; currently only use Facebook C# SDK library for parsing Signed Request

- IFacebookConfiguration or similar class with AppId, AppSecret properties to play well with DI container and multiple apps per website

- rarely require full friends list on server; render on client side using Facebook Javascript SDK and client side templates; instead on server use FQL for "Application Friends" ie. only your friends who have authenticated the app, for example friend's highscores

I was wanting to explore how these choices would fit with those made in the Microsoft.AspNet.Mvc.Facebook.Controllers code.

Hope this is of interest.

Andy

Oct 24, 2012 at 8:35 PM

Cool that you found it already without any announcement or anything. ;)

Nice list of stuff. Thanks for sharing! We'd definitely love to hear your feedback after you get a chance to play with it. You should find that some of the things on your list are in there, some are planned moving forward and others might make sense to contribute if you're willing. We do take pull requests. :)

Thanks for checking it out and let us know what you think!

Nov 1, 2012 at 10:23 AM
Edited Nov 1, 2012 at 1:56 PM

Hi Erik

The project template does certainly seem like a good quick start for a Canvas Application. (I'll admit I mainly used and looked around the Nuget package of the code and Codeplex, rather than the VS installer.) Some general feedback is below.

- the typical model objects on the Canvas Application home route are Signed Request, Request Ids, Facebook Source if the native facebook flow is used; this seems a little hidden
- would normally associate Canvas homepage route with an AccountController type control logic, accessing user and friends list from an FacebookService rather than model binders
- we use an attribute IframeCookieSupportAttribute to allow forms authentication cookies cross browser within an iframe
- nearly all apps have a companion Tab Application; and adding a MobileController would create a good placeholder
- no common access to Signed Request and Access Token from within a controller; shame adding access token in ViewBag rather than an IPrincipal
- canvas authentication redirect results is very userful so a seperate ActionResult would be good for reuse
- the template should add Terms and Privacy actions to remined developers that these are required for Canvas Applications
- there is quite a variety in recent App Settings naming conventions in frameworks "facebook:AppSecret" would be my preference, as with Razor
- wonder whether getting full friends list is a good sample, as this can be a very long list; would be more explicit using async controllers
- worry that most apps now use javascript Facebook Auth Dialog, as Canvas homepage normally has to promote and invite new users
Pseudo code
public class CanvasController : Controller {
     [FacebookAuthorize(Permissions="email")]
     [IframeCookieSupport]
     public async Task<ActionResult> Index(FacebookSignedRequest signedRequest, FacebookSource facebookSource, string[] requestIds) {
         if(!string.IsNullOrEmpty(signedRequest.UserId)) {
              FacebookAuthentication.SetAuthCookie(signedRequest.UserId, signedRequest.AccessToken);
         }
         var me = FacebookService.GetMeAsync(signedRequest.AccessToken);
         var appFriends = FacebookService.GetAppFriendsAsync(signedRequest.AccessToken);
         await Task.WhenAll(me, appFriends);
         ViewBag.Me = me.Result;
         ViewBag.AppFields = appFriends.Result;
         return View();
     }
     public ActionResult TermsAndConditions() {
         return View();
     } 
     public ActionResult PrivacyPolicy() {
         return View();
     }
}
public class TabController : Controller {
     public ActionResult Index(FacebookSignedRequest signedRequest) {
         if(!signedRequest.Page.Liked) {
              return View("LikeGate");
} else {
              return View();
         }
     }
}
public class MobileController : Controller {
     [FacebookAuthorize(Permissions="email")]
     public ActionResult Index(string[] requestIds) {
          return View();
     }
}
public class FacebookAuthorizeRedirectResult : ActionResult {
    ....
}
public class FacebookUser : IPrincipal {
    public string FacebookId { get; }
    public string AccessToken { get; }
    ....
}
public static class FacebookPrincipalExtension {
    public static FacebookUser AsFacebookUser(this IPrincipal user) {
        ....
    }
public class IframeCookieSupportAttribute : ActionFilterAttribute
{
    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        var response = filterContext.HttpContext.Response;
        if (!filterContext.IsChildAction && !response.IsRequestBeingRedirected)
        {
                response.AddHeader("p3p", "CP=\"CAO PSA OUR\"");
        }
    }
}
Cheers.
Andy
Nov 5, 2012 at 5:45 PM

Hi

Prototype: https://github.com/andybooth/instatus/tree/master/Instatus.Integration.Facebook

[IframeCookieSupport]
    public class CanvasController : Controller
    {
        public ActionResult Index(FacebookSignedRequest signedRequest)
        {
            if (signedRequest.OauthToken != null)
            {
                FacebookAuthentication.SetAuthCookie(signedRequest.UserId, signedRequest.OauthToken);
            }
            else
            {
                return new FacebookAuthDialogResult();
            }
            
            return Content("User: " + signedRequest.UserId);
        }
[Authorize]
        public ActionResult SecondPage()
        {
            return Content("User: " + User.Identity.Name + " AccessToken:" + User.GetAccessToken());
        }
    }

Seems quite a simple effective approach, with more direct access to the signed request.

Andy