web-dev-qa-db-fra.com

L’authentification Facebook ASP.NET MVC5 OWIN Facebook ne fonctionne tout à coup pas

Mise à jour 2017!

Le problème que j'ai rencontré lorsque j'ai posté la question initiale n'a rien à voir avec les récents changements apportés par Facebook lorsqu'ils ont forcé tout le monde à utiliser la version 2.3 de leur API. Pour une solution à ce problème spécifique, voir la réponse de sammy34 ci-dessous. La version 2.3 du noeud final/oauth/access_token renvoie maintenant JSON au lieu de valeurs codées par formulaire.

Pour des raisons historiques, voici ma question/question initiale:

J'ai une application Web MVC5 qui utilise le support intégré pour l'authentification via Facebook et Google. Lorsque nous avons créé cette application il y a quelques mois, nous avons suivi ce tutoriel: http://www.asp.net/mvc/tutorials/mvc-5/create-an-aspnet-mvc-5-app-with-facebook -and-google-oauth2-and-openid-sign-on et tout a bien fonctionné.

Tout à coup, l'authentification Facebook a complètement cessé de fonctionner. L'authentification Google fonctionne toujours très bien.

Description du problème: Nous cliquons sur le lien pour nous connecter via Facebook. Nous sommes redirigés vers Facebook où nous sommes invités à autoriser l’accès de notre application Facebook à notre profil. Lorsque nous cliquons sur "OK", nous sommes redirigés vers notre site, mais au lieu d'être connectés, nous nous retrouvons simplement à l'écran de connexion.

J'ai suivi ce processus en mode débogage et j'ai ce ActionResult dans mon contrôleur de compte selon le tutoriel mentionné ci-dessus:

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

Lorsque vous parcourez le code et que vous revenez de Facebook, l'objet loginInfo est toujours NULL, ce qui entraîne la redirection de l'utilisateur vers la connexion.

Afin de comprendre ce qui se passe réellement dans les coulisses, j'ai installé Fiddler et surveillé le trafic HTTP. Ce que j’ai découvert, c’est qu’après avoir cliqué sur “OK” dans la boîte de dialogue d’autorisation de Facebook, Facebook redirige vers notre application avec cette URL:

https://localhost/signin-facebook?code=<access-token>

Cette URL n’est pas un fichier réel et est probablement gérée par un contrôleur/gestionnaire intégré à la structure OWIN que je devine. Très probablement, il se connecte à Facebook en utilisant le code donné pour demander des informations sur l'utilisateur qui tente de se connecter. Le problème est qu’au lieu de le faire, nous sommes redirigés vers:

/Account/ExternalLoginCallback?error=access_denied

Ce que je suis sûr que Facebook fait, c’est-à-dire qu’au lieu de nous fournir les données de l’utilisateur, il nous redirige avec ce message d’erreur.

Cela provoque l'échec de AuthenticationManager.GetExternalLoginInfoAsync(); et renvoie toujours NULL.

Je suis complètement à court d'idées. Autant que nous sachions, nous n'avons rien changé de notre côté.

J'ai essayé de créer une nouvelle application Facebook, j'ai de nouveau suivi le didacticiel, mais j'ai toujours le même problème.

Toutes les idées sont les bienvenues!

Mettre à jour!

OK, cela me rend fou! J'ai maintenant manuellement effectué les étapes nécessaires pour effectuer l'authentification et tout fonctionne parfaitement lorsque je le fais. Pourquoi diable cela ne fonctionne-t-il pas lorsque vous utilisez le logiciel MVC5 Owin?

C'est ce que j'ai fait:

    // Step 1 - Pasted this into a browser, this returns a code
    https://www.facebook.com/dialog/oauth?response_type=code&client_id=619359858118523&redirect_uri=https%3A%2F%2Flocalhost%2Fsignin-facebook&scope=&state=u9R1m4iRI6Td4yACEgO99ETQw9NAos06bZWilJxJrXRn1rh4KEQhfuEVAq52UPnUif-lEHgayyWrsrdlW6t3ghLD8iFGX5S2iUBHotyTqCCQ9lx2Nl091pHPIw1N0JV23sc4wYfOs2YU5smyw9MGhcEuinvTAEql2QhBowR62FfU6PY4lA6m8pD3odI5MwBYOMor3eMLu2qnpEk0GekbtTVWgQnKnH6t1UcC6KcNXYY

I was redirected back to localhost (which I had shut down at this point to avoid being redirected immediately away).  The URL I was redirected to is this:

https://localhost/signin-facebook?code=<code-received-removed-for-obvious-reasons>

Now, I grabbed the code I got and used it in the URL below:

// Step 2 - opened this URL in a browser, and successfully retrieved an access token
https://graph.facebook.com/oauth/access_token?client_id=619359858118523&redirect_uri=https://localhost/signin-facebook&client_secret=<client-secret>&code=<code-from-step-1>

// Step 3 - Now I'm able to query the facebook graph using the access token from step 2!

https://graph.facebook.com/me?access_token=<access-token-from-step-2>

Aucune erreur, tout fonctionne très bien! Alors pourquoi diable cela ne fonctionne-t-il pas lorsque vous utilisez le logiciel MVC5 Owin? Il est évident que quelque chose ne va pas avec la mise en œuvre de OWin.

65
HaukurHaf

Ok j'ai une solution au problème.

Voici le code que j'avais précédemment dans mon fichier Startup.Auth.cs:

var x = new FacebookAuthenticationOptions();
            //x.Scope.Add("email");
            x.AppId = "1442725269277224";
            x.AppSecret = "<secret>";
            x.Provider = new FacebookAuthenticationProvider()
            {
                OnAuthenticated = async context =>
                {
                        //Get the access token from FB and store it in the database and
                    //use FacebookC# SDK to get more information about the user
                    context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken",context.AccessToken));
                    context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:name", context.Name));
                    context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:email", context.Email));
                }
            };
            x.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;
            app.UseFacebookAuthentication(x);

Remarquez comment 

x.Scope.Add("email")

la ligne a été commentée, mais je demande quand même le courrier électronique plus tard dans le gestionnaire OnAuthenticated? Ouais, c'est vrai. Pour une raison quelconque, cela a fonctionné parfaitement pendant quelques semaines.

Ma solution était simplement de ne pas commenter le x.Scope.Add ("email"); ligne pour vous assurer que la variable scope = email était présente dans la demande initiale à Facebook.

Maintenant tout fonctionne comme avant!

Je ne peux pas comprendre pourquoi cela fonctionnait auparavant comme avant. La seule explication que je puisse trouver est que Facebook a changé quelque chose de leur côté.

9
HaukurHaf

Mise à jour du 22 avril 2017: La version 3.1.0 des packages Microsoft.Owin. * Est maintenant disponible. Si vous rencontrez des problèmes après la modification de l'API de Facebook à compter du 27 mars 2017, essayez d'abord les packages mis à jour de NuGet. Dans mon cas, ils ont résolu le problème (fonctionne bien sur nos systèmes de production).

Réponse originale:

Dans mon cas, je me suis réveillé le 28 mars 2017 pour découvrir que l'authentification Facebook de notre application avait soudainement cessé de fonctionner. Nous n'avions rien changé dans le code de l'application.

Il s'avère que Facebook a effectué une "mise à niveau forcée" de son API graphique de la version 2.2 à la version 2.3 le 27 mars 2017. L'une des différences entre ces versions de l'API semble être que le point de terminaison Facebook /oauth/access_token ne répond plus par un formulaire. contenu codé corps, mais avec JSON à la place.

Maintenant, dans le middleware Owin, nous trouvons la méthode protected override FacebookAuthenticationHandler.AuthenticateCoreAsync(), qui analyse le corps de la réponse sous forme de formulaire et utilise ensuite le access_token du formulaire analysé. Inutile de dire que le formulaire analysé est vide, le access_token est également vide, ce qui entraîne une erreur access_denied plus loin dans la chaîne.

Pour résoudre ce problème rapidement, nous avons créé une classe wrapper pour la réponse Facebook Oauth.

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

Ensuite, dans OwinStart, nous avons ajouté un gestionnaire de canal arrière personnalisé ...

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

... où le gestionnaire est défini comme:

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;
    }
}

Fondamentalement, le gestionnaire crée simplement un nouveau HttpResponseMessage contenant les informations codées en forme équivalentes à partir de la réponse JSON de Facebook. Notez que ce code utilise le package Json.Net populaire.

Avec ce gestionnaire personnalisé, les problèmes semblent résolus (bien que nous n’ayons pas encore déployé pour prod :)).

J'espère que cela sauvera quelqu'un d'autre qui se réveille aujourd'hui avec des problèmes similaires!

Aussi, si quelqu'un a une solution plus propre à cela, j'aimerais bien le savoir!

109
sammy34

Remarqué ce problème hier. Facebook ne prend plus en charge la version 3.0.1 de Microsoft.Owin.Security.Facebook. Pour moi, cela a fonctionné pour installer la version 3.1.0. Pour mettre à jour vers la version 3.1.0, exécutez la commande Install-Package Microsoft.Owin.Security.Facebook dans la console du gestionnaire de package: https://www.nuget.org/packages/Microsoft.Owin.Security.Facebook

26
JayPi

J'ai eu le même problème avec l'authentification Google. Ce qui suit a fonctionné pour moi: Modifications apportées à Google OAuth 2.0 et mises à jour dans les middleware Google pour la version 3.0.0 RC

4
Maarten Docter

La dernière mise à jour de Facebook a eu lieu le 2015-02-09 ( https://www.nuget.org/packages/Microsoft.AspNet.WebPages.OAuth/ )

La dernière version de l'API à ce moment-là était la version 2.2. La version 2.2 est arrivée à expiration le 25 mars 2017, date du début du problème. ( https://developers.facebook.com/docs/apps/changelog )

J'imagine que Facebook a probablement mis à jour automatiquement l'API et que la bibliothèque MS OAUTH n'est plus en mesure d'analyser la nouvelle réponse.

tldr: la bibliothèque Microsoft WebPages OAuth est obsolète (du moins pour FB) et vous devrez probablement trouver une autre solution

3
RedTornado

Les solutions ci-dessus ne fonctionnaient pas pour moi. À la fin, cela semblait être lié à la session. En "réveillant" la session de l'appel précédent, elle ne renverrait plus la valeur null à partir de GetExternalLoginInfoAsync ()

    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public ActionResult ExternalLogin(string provider, string returnUrl)
    {
        Session["WAKEUP"] = "NOW!";
        // Request a redirect to the external login provider
        return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
    }

Comme pour l’opération, l’autorisation d’un tiers a fonctionné pendant un bon bout de temps, puis tout à coup, cela s’est arrêté. Je pense que cela était dû aux modifications apportées à mon code lorsque j'ai configuré la session pour qu'elle utilise Redis Cache sur Azure.

2
Joel Gallagher

J'ai eu ce problème aussi, mais cela n'a pas été causé par le réglage de la portée. Il m'a fallu beaucoup de temps pour comprendre cela, mais ce qui m'a finalement éclairé, c'est de définir un enregistreur personnalisé en définissant ce qui suit dans OwinStartup.Configuration(IAppBuilder app).

app.SetLoggerFactory(new LoggerFactory()); 
// Note: LoggerFactory is my own custom ILoggerFactory

Cela a abouti à ce qui suit:

2014-05-31 21: 14: 48,508 [8] ERREUR
Microsoft.Owin.Security.Cookies.CookieAuthenticationMiddleware
[(null)] - 0x00000000 - L'authentification a échoué
System.Net.Http.HttpRequestException: une erreur s'est produite lors de l'envoi la demande. ---> System.Net.WebException: le nom distant n'a pas pu
être résolu: 'graph.facebook.com' à
System.Net.HttpWebRequest.EndGetResponse (IAsyncResult asyncResult)
at System.Net.Http.HttpClientHandler.GetResponseCallback (IAsyncResult ar.) --- Fin de la trace de pile d'exception interne --- at
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (tâche
tâche) à
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (tâche Tâche) à System.Runtime.CompilerServices.TaskAwaiter`1.GetResult () à
Microsoft.Owin.Security.Facebook.FacebookAuthenticationHandler.d__0.MoveNext ()

Sur la base de la pile d'appels ci-dessus, j'ai constaté que mon Azure VM était incapable de résoudre graph.facebook.com. Tout ce que j'avais à faire pour résoudre ce problème était d'exécuter "ipconfig/registerdns" et j'étais tout à fait résolu ...

2
jt000

Je travaille sur une solution depuis trois jours. Et je viens de le trouver sur github ( https://github.com/aspnet/AspNetKatana/issues/38#issuecomment-290400987 )

var facebookOptions = new FacebookAuthenticationOptions()
{
    AppId = "xxxxx",
    AppSecret = "xxxxx",
};

// Set requested scope
facebookOptions.Scope.Add("email");
facebookOptions.Scope.Add("public_profile");

// Set requested fields
facebookOptions.Fields.Add("email");
facebookOptions.Fields.Add("first_name");
facebookOptions.Fields.Add("last_name");

facebookOptions.Provider = new FacebookAuthenticationProvider()
{
    OnAuthenticated = (context) =>
        {
            // Attach the access token if you need it later on for calls on behalf of the user
            context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));

            foreach (var claim in context.User)
            {
                //var claimType = string.Format("urn:facebook:{0}", claim.Key);
                var claimType = string.Format("{0}", claim.Key);
                string claimValue = claim.Value.ToString();

                    if (!context.Identity.HasClaim(claimType, claimValue))
                        context.Identity.AddClaim(new System.Security.Claims.Claim(claimType, claimValue, "XmlSchemaString", "Facebook"));
            }

            return Task.FromResult(0);
       }
};

app.UseFacebookAuthentication(facebookOptions);

Et pour avoir des valeurs

var info = await AuthenticationManager.GetExternalLoginInfoAsync();

if (info != null)
{
    var firstName = info.ExternalIdentity.Claims.First(c => c.Type == "first_name").Value;
    var lastName = info.ExternalIdentity.Claims.First(c => c.Type == "last_name").Value;
}
1
affair

Vérifiez que vous obtenez une connexion Internet externe à partir de votre application. Sinon, réparez votre connexion Internet externe. Mon problème était que j'utilisais une instance EC2 AWS qui a soudainement cessé de se connecter à Internet. Il m'a fallu un certain temps pour comprendre que c'était le problème.

0
Ronen Festinger

Cela m'a rendu fou. Tout fonctionnait jusqu'à mon déploiement dans mon environnement de transfert. J'utilisais Microsoft.Owin.Security.Facebook version 3.0.1 de Nuget. J'ai mis à jour la version préliminaire 3.1.0 de Nuget et je n'ai plus l'erreur d'accès refusé ...

0
Ewert

Même si j'ai tout fait ce que sammy34 avait dit, cela ne fonctionnait pas pour moi. J'étais au même point avec HaukurHaf: lorsque je fais apirequest manuellement sur le navigateur, cela fonctionne parfaitement, mais si j'utilise mon application MVC, GetExternalLoginInfoAsync() renvoie toujours null.

J'ai donc changé quelques lignes sur les codes de sammy34 comme sur ce commentaire: https://stackoverflow.com/a/43148543/7776015

Remplacé:

if (!request.RequestUri.AbsolutePath.Contains("/oauth"))
{
request.RequestUri = new Uri(request.RequestUri.AbsoluteUri.Replace("?access_token", "&access_token"));
}
var result = await base.SendAsync(request, cancellationToken);
if (!request.RequestUri.AbsolutePath.Contains("/oauth"))
{
return result;
}

Au lieu de:

var result = await base.SendAsync(request, cancellationToken);
if (!request.RequestUri.AbsolutePath.Contains("access_token"))
return result;

Et ajouté cette ligne dans ma FacebookAuthenticationOptions:

UserInformationEndpoint = "https://graph.facebook.com/v2.8/me?fields=id,name,email,first_name,last_name,picture"

et maintenant ça marche. (champs et paramètres optionnels)

Note: je n'ai pas mis à jour Microsoft.Owin.Security.Facebook 

0
gld-R