ASP.NET MVC5 OWIN Facebook authentication suddenly not working

Update 22nd April 2017: Version 3.1.0 of the Microsoft.Owin.* packages are now available. If you’re having problems after Facebook’s API changes from the 27th March 2017, try the updated NuGet packages first. In my case they solved the problem (working fine on our production systems).

Original answer:

In my case, I woke up on the 28th March 2017 to discover that our app’s Facebook authentication had suddenly stopped working. We hadn’t changed anything in the app code.

It turns out that Facebook did a “force upgrade” of their graph API from version 2.2 to 2.3 on 27th March 2017. One of the differences in these versions of the API seems to be that the Facebook endpoint /oauth/access_token responds no longer with a form-encoded content body, but with JSON instead.

Now, in the Owin middleware, we find the method protected override FacebookAuthenticationHandler.AuthenticateCoreAsync(), which parses the body of the response as a form and subsequently uses the access_token from the parsed form. Needless to say, the parsed form is empty, so the access_token is also empty, causing an access_denied error further down the chain.

To fix this quickly, we created a wrapper class for the Facebook Oauth response

public class FacebookOauthResponse
{
    public string access_token { get; set; }
    public string token_type { get; set; }
    public int expires_in { get; set; }
}

Then, in OwinStart, we added a custom back-channel handler…

        app.UseFacebookAuthentication(new FacebookAuthenticationOptions
        {
            AppId = "hidden",
            AppSecret = "hidden",
            BackchannelHttpHandler = new FacebookBackChannelHandler()
        });

…where the handler is defined as:

public class FacebookBackChannelHandler : HttpClientHandler
{
    protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        var result = await base.SendAsync(request, cancellationToken);
        if (!request.RequestUri.AbsolutePath.Contains("access_token"))
            return result;

        // For the access token we need to now deal with the fact that the response is now in JSON format, not form values. Owin looks for form values.
        var content = await result.Content.ReadAsStringAsync();
        var facebookOauthResponse = JsonConvert.DeserializeObject<FacebookOauthResponse>(content);

        var outgoingQueryString = HttpUtility.ParseQueryString(string.Empty);
        outgoingQueryString.Add(nameof(facebookOauthResponse.access_token), facebookOauthResponse.access_token);
        outgoingQueryString.Add(nameof(facebookOauthResponse.expires_in), facebookOauthResponse.expires_in + string.Empty);
        outgoingQueryString.Add(nameof(facebookOauthResponse.token_type), facebookOauthResponse.token_type);
        var postdata = outgoingQueryString.ToString();

        var modifiedResult = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent(postdata)
        };

        return modifiedResult;
    }
}

Basically, the handler simply creates a new HttpResponseMessage containing the equivalent form-encoded information from the Facebook JSON response. Note that this code uses the popular Json.Net package.

With this custom handler, the problems seem to be resolved (although we’re yet to deploy to prod :)).

Hope that saves somebody else waking up today with similar problems!

Also, if anybody has a cleaner solution to this, I’d love to know!

Leave a Comment