asp.net-mvcasp.net-identityowinfacebook-loginstructuremap3

MVC5 and OWIN facebook authentication provider not working in IE11 and Firefox (but works in Chrome)


I'm using Asp.Net MVC 5 and utilizing the Microsoft's Identity (v2) implementation for authentication and external (facebook) login.

My code for the external login is like this:

in Startup.Auth.cs I declare the provider:

var facebookAuthenticationOptions = new FacebookAuthenticationOptions();
        facebookAuthenticationOptions.Scope.Add("email");
        (...)
        facebookAuthenticationOptions.AppId = "<my fb app id>";
        facebookAuthenticationOptions.AppSecret = "<my fb app secret>";
        facebookAuthenticationOptions.Provider = new FacebookAuthenticationProvider()
        {
            OnAuthenticated = async context =>
            {
                //Get the access token from FB and store it in the database
                context.Identity.AddClaim(
                new System.Security.Claims.Claim("FacebookAccessToken",
                                                     context.AccessToken));
            }
        };
        app.UseFacebookAuthentication(facebookAuthenticationOptions);

in a Controller function that I post the facebook login form,

[HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public ActionResult ExternalLogin(string provider, string returnUrl)
    {

        returnUrl = !String.IsNullOrEmpty(returnUrl) ? returnUrl : "/";

        // Request a redirect to the external login provider
        return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
        return null;
    }

and with this in my base controller (that I derive all my controllers from), I use owin OAuth:

internal class ChallengeResult : HttpUnauthorizedResult
    {
        public ChallengeResult(string provider, string redirectUri)
            : this(provider, redirectUri, null)
        {
        }

        public ChallengeResult(string provider, string redirectUri, string userId)
        {
            LoginProvider = provider;
            RedirectUri = redirectUri;
            UserId = userId;
        }

        public string LoginProvider { get; set; }
        public string RedirectUri { get; set; }
        public string UserId { get; set; }

        public override void ExecuteResult(ControllerContext context)
        {
            var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
            if (UserId != null)
            {
                properties.Dictionary[XsrfKey] = UserId;
            }
            context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
        }

    }

I then get the callback with an external callback function (as is seen that I've declared in the ChallangeResult object above):

[AllowAnonymous]
    public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
    {
        var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
        if (loginInfo == null)
        {
            return RedirectToAction("Login");
        }

        // Sign in the user with this external login provider if the user already has a login
        var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);

        switch (result)
        {

        // login logic code here.    
        // and return some View(""); or RedirerctToAction("somewhere");
        }

    }

Also noteworthy to add, I'm using StructureMap 3.0 for dependency injection, and, as per the following code it limits the life span cycle of any controller code to the life span of a request.

public void Init(HttpApplication context) {
        context.BeginRequest += (sender, e) => StructuremapMvc.StructureMapDependencyScope.CreateNestedContainer();
        context.EndRequest += (sender, e) => {
            HttpContextLifecycle.DisposeAndClearAll();
            StructuremapMvc.StructureMapDependencyScope.DisposeNestedContainer();
        };
    }

Now, this all works well and does take care of the facebook login for me when I run the application from chrome. But when I use internet explorer (tried 11) or firefox it hangs in a black screen at /{controller}/ExternalLogin and does not do any redirecting.

If this was an issue with the IOC containment of structuremap, I believe it shouldn't have run smoothly on chrome as well. So I've been searching all over the web and couldn't find a solution - or any indication of what is going on. And since I don't get any exceptions or the like, I can't figure out what I'm doing wrong.

To sum up:

Thanks.


Solution

  • Ok, I was thinking a bit too complex. The problem was in the input tag I was posting.

    if your post action is taking the form data like public ActionResult ExternalLogin(string provider, string returnUrl) (i.e. no model) and you use an input image's name and value to feed that form action like:

    <input type="image" id="@facebookLogin.AuthenticationType" name="provider" value="@facebookLogin.AuthenticationType"...>
    

    then, for some reason Chrome sends correct post data whereas internet explorer and firefox can't.

    So no dependecy injection, middleware or OAuth problems, just plain old html with this one.