Over the past several months Facebook made changes to their application development APIs that were incompatible with the MVC Facebook support.
We have been working on updates while the Facebook API kept evolving, and on 4/30/2014 Facebook announced a two-year stability guarantee. This was a fantastic announcement because this ensured a similar stability for ASP.NET MVC developers developing Facebook applications. We've fixed the Facebook package and renamed it to Microsoft.AspNet.Facebook. This package is now available on NuGet.
Here are the issues that we’ve fixed.
If you’re new to the world of Facebook application development on MVC you can check out a Birthday application here. Keep in mind this tutorial will be gradually updated so it may not be entirely accurate.
The important new stuff
The original Microsoft.AspNet.Mvc.Facebook package and corresponding API’s at the time had no concept of optional or default permissions. This created friction with some of the updated prompt dialogs that Facebook released. To address this we’ve made the Facebook’s authorize filter more flexible by providing permission prompt hooks to control login dialog flow.
FacebookAuthorizeFIlter now exposes OnPermissionPrompt and an OnDeniedPermissionPrompt hooks.So what do these hooks do? Let’s go over each in detail.
OnPermissionPrompt
Every time a prompt is about to be shown the OnPermissionPrompt is invoked and passed a PermissionContext. The hook enables you to modify the login flow by setting the context's Result property just like you’d do in an authorization filter.
To utilize the OnPermissionPrompt you can create a FacebookAuthorizeFilter like so:
public class CustomFacebookAuthorizeFilter : FacebookAuthorizeFilter{public CustomFacebookAuthorizeFilter(FacebookConfiguration config) : base(config) { }protected override void OnPermissionPrompt(PermissionContext context) {// This sets context.Result to ShowPrompt(context) (default behavior)base.OnPermissionPrompt(context); } }
And then you can apply the filter the same way you’d apply a FacebookAuthorizeFilter in the old world:
GlobalFilters.Filters.Add(new CustomFacebookAuthorizeFilter(yourFacebookConfiguration));
The default behavior of the OnPermissionPrompt hook is to set the PermissionContext's result to a ShowPromptActionResult by calling into the ShowPrompt method. The source code of the OnPermissionPrompt method looks like this:
protected virtual void OnPermissionPrompt(PermissionContext context) { context.Result = ShowPrompt(context); }
The ShowPrompt method returns an action result that shows the permission prompt to the user. We can do more though; if we really wanted to, we could redirect the user to a different action every time a prompt was about to be shown by overriding the default signature as shown:
protected override void OnPermissionPrompt(PermissionContext context) { context.Result = new RedirectToRouteResult(new RouteValueDictionary{ { "controller", "home" }, { "action", "foo" } }); }
This would redirect us to the Home controller’s action Foo instead of prompting the user for permissions.
Lastly we could modify the OnPermissionPrompt method to ignore every prompt that’s shown via setting the PermissionContext's Result to be null:
protected override void OnPermissionPrompt(PermissionContext context) { context.Result = null; }
This would make it so we never prompt a user for permissions. This isn’t ideal but it is possible.
OnDeniedPermissionPrompt
The OnDeniedPermissionPrompt is invoked when we detect that a user has revoked, declined, or skipped permissions. This occurs instead of the OnPermissionPrompt hook when there are denied permissions. Just like the OnPermissionPrompt we can utilize this hook by creating a FacebookAuthorizeFilter like so:
public class CustomFacebookAuthorizeFilter : FacebookAuthorizeFilter{public CustomFacebookAuthorizeFilter(FacebookConfiguration config) : base(config) { }protected override void OnDeniedPermissionPrompt(PermissionContext context) {// Does nothing (default behavior to leave context.Result null)base.OnDeniedPermissionPrompt(context); } }
And then just like the OnPermissionPrompt above you can apply the filter the same way you’d apply a FacebokAuthorizeFilter in the old world:
GlobalFilters.Filters.Add(new CustomFacebookAuthorizeFilter(yourFacebookConfiguration));
The default behavior of the OnDeniedPermissionPrompt hook is to leave the passed in PermissionContext’s Result member null to ignore the “denied” permission prompt. The source code of the OnDeniedPermissionPrompt method looks like this:
protected virtual void OnDeniedPermissionPrompt(PermissionContext context) { }
Here we’re doing nothing so the PermissionContext's Result member is null; this indicates that we do not want to show the permission prompt to the user. If we were to do the same thing as OnPermissionPrompt and set the PermissionContext's Result to be a ShowPromptActionResult we’d infinite loop in our login dialogs; the reason why is because every time the method is invoked there would still be denied permissions.
Like the OnPermissionPrompt hook you can modify the login flow with the result that you return. For example, let’s say we wanted to redirect to a “skip” handling page if we detect a user has skipped permissions:
protected override void OnDeniedPermissionPrompt(PermissionContext context) {if (context.SkippedPermissions.Any()) { context.Result = new RedirectResult("http://www.contoso.com/SomeUrlToHandleSkips"); } }
PermissionContext
In the previous sections I did not discuss the PermissionContext object that is passed into both the OnPermissionPrompt and OnDeniedPermissionPrompt hooks. This object exposes a significant amount of information that enable developers to modify the login flow. Let’s examine what the PermissionContext has to offer:
- IEnumerable<string> DeclinedPermissions
- Permissions that were previously requested for but not granted for the lifetime of the application. This can happen by a user revoking, skipping or choosing not to allow permissions in the Facebook login dialog.
- FacebookContext FacebookContext
- Provides access to Facebook-specific information.
- AuthorizationContext FilterContext
- Provides access to filter information.
- IEnumerable<string> MissingPermissions
- The entire list of missing permissions for the current page, including DeclinedPermissions and SkippedPermissions.
- HashSet<string> RequiredPermissions
- The entire list of requested permissions for the current page. Includes permissions that were already prompted for.
- IEnumerable<string> SkippedPermissions
- Permissions that were previously requested for but skipped. This can happen from a user hitting the "skip" button when requesting permissions.
- ActionResult Result
- The ActionResult that should be used to control the login flow. If value is null then we will continue onto the action that is intended to be invoked. Non-null values short-circuit the action.
With all of the information the PermissionContext has to offer you should be able to fully control the login flow.
Hook flow chart
For reference sake I’ve created a flow chart to indicate how Facebook’s login dialog and our hooks interact.
Upgrading Existing Facebook Applications
To upgrade an existing Facebook application follow these steps:
- If your application does not currently run; migrate to the latest Facebook application platform.
- In the package manager console run: Uninstall-Package Microsoft.AspNet.Mvc.Facebook
- Rename all Microsoft.AspNet.Mvc.Facebook namespaces to Microsoft.AspNet.Facebook.
- In the package manager console run: Install-Package Microsoft.AspNet.Facebook
Conclusion
We’d love to see people try out the new Microsoft.AspNet.Facebook package and let us know what you think. If you have any questions feel free to reach out below.
To find or file issues do so here.
Note: Facebook changed the way their “user_friends” permission works. It used to return all of a users friends and now only returns friends that also have your application. This will obviously be a limiter when using the existing Facebook template and the Birthday App tutorial.