Проверка подлинности ASP.NET MVC5 OWIN в Facebook вдруг не работает


обновить 2017!

проблема, с которой я столкнулся, когда я опубликовал исходный вопрос, не имеет ничего общего с недавними изменениями Facebook, сделанными, когда они заставили всех перейти на версию 2.3 своего API. Решение этой конкретной проблемы см. В разделе ответ сэмми34 ниже. Версия 2.3 конечной точки/oauth / access_token теперь возвращает JSON вместо закодированных в форме значений

по историческим причинам, вот мой оригинал вопрос:

у меня есть веб-приложение MVC5, которое использует встроенную поддержку аутентификации через Facebook и Google. Когда мы создали это приложение несколько месяцев назад, мы следовали этому учебнику: http://www.asp.net/mvc/tutorials/mvc-5/create-an-aspnet-mvc-5-app-with-facebook-and-google-oauth2-and-openid-sign-on и все работало отлично.

теперь, внезапно, аутентификация Facebook просто перестала работать все вместе. Этот Аутентификация Google по-прежнему отлично работает.

описание проблемы: мы нажимаем ссылку для подключения с помощью Facebook, мы перенаправлены на Facebook, где нам будет предложено, если мы не хотим, чтобы наше приложение Facebook доступ к нашему профилю. Когда мы нажимаем "OK", мы перенаправляемся обратно на наш сайт, но вместо того, чтобы войти в систему, мы просто оказываемся на экране входа в систему.

Я прошел через этот процесс в режиме отладки, и у меня есть этот ActionResult в моем контроллере учетной записи в соответствии с учебник, упомянутый выше:

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

при переходе через код и при возвращении из Facebook объект loginInfo всегда имеет значение NULL,что приводит к перенаправлению пользователя обратно к логину.

чтобы понять, что на самом деле происходит за кулисами, я установил Fiddler и контролировал HTTP-трафик. Я обнаружил, что при нажатии кнопки "ОК" в диалоговом окне разрешения Facebook Facebook перенаправляет обратно в наше приложение с помощью этого URL:

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

этот URL-адрес не является фактическим файлом и, вероятно, обрабатывается каким-то контроллером/обработчиком, встроенным в эту структуру OWIN, я предполагаю. Скорее всего, он подключается обратно к Facebook, используя данный код для запроса информации о пользователе, который пытается войти в систему. Теперь проблема в том, что вместо этого мы перенаправляемся на:

/Account/ExternalLoginCallback?error=access_denied

что я уверен, что это то, что делает Facebook, то есть вместо того, чтобы давать нам данные пользователя, он перенаправляет нас вернуться с этим сообщением об ошибке.

в этом случае AuthenticationManager.GetExternalLoginInfoAsync(); для сбоя и всегда возвращать NULL.

у меня совершенно нет идей. Насколько нам известно, мы ничего не меняли с нашей стороны.

Я попытался создать новое приложение Facebook, я попытался снова следовать учебнику, но у меня всегда есть одна и та же проблема.

любые идеи приветствуются!

обновление!

ОК, это сводит меня с ума! Теперь я вручную прошел шаги, необходимые для выполнения аутентификации, и все отлично работает, когда я это делаю. Почему это не работает при использовании материала Mvc5 Owin?

вот что я сделал:

    // 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>

нет ошибок, все работает отлично! Тогда почему, черт возьми, это не работает при использовании материала Mvc5 Owin? Очевидно, что-то не так с реализацией OWin.

12 63

12 ответов:

обновление 22 апреля 2017: версия 3.1.0 от Microsoft.Долг.* пакеты теперь доступны. Если у вас возникли проблемы после изменения API Facebook с 27 марта 2017 года, сначала попробуйте обновленные пакеты NuGet. В моем случае они решили проблему (отлично работают на наших производственных системах).

оригинальный ответ:

в моем случае я проснулся 28 марта 2017 года, чтобы обнаружить, что аутентификация Facebook нашего приложения внезапно перестал работать. Мы ничего не изменили в коде приложения.

оказывается, что Facebook сделал "принудительное обновление" своего API graph с версии 2.2 до 2.3 27 марта 2017 года. Одним из отличий в этих версиях API, по-видимому, является то, что конечная точка Facebook /oauth/access_token отвечает больше не с закодированным в форме телом контента, а с JSON вместо этого.

теперь, в промежуточном программном обеспечении Owin, мы находим метод protected override FacebookAuthenticationHandler.AuthenticateCoreAsync(), который анализирует тело ответа как форму и впоследствии использует access_token из разобранной форме. Излишне говорить, что разбираемая форма пуста, поэтому access_token также пуст, вызывая access_denied ошибка дальше по цепочке.

чтобы исправить это быстро, мы создали класс-оболочку для ответа Facebook Oauth

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

затем в OwinStart мы добавили пользовательский обработчик обратного канала...

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

...где обработчик определяется как:

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

в принципе, обработчик просто создает новый HttpResponseMessage, содержащий эквивалентную закодированную в форме информацию из ответа Facebook JSON. Обратите внимание, что этот код использует популярные Json.Net пакет.

С помощью этого пользовательского обработчика проблемы, похоже, решены (хотя нам еще предстоит развернуть prod :)).

надеюсь, что это спасает кого-то еще просыпаться сегодня с подобными проблемами!

кроме того, если у кого-то есть более чистое решение для этого, я хотел бы знать!

заметил эту проблему. Facebook не поддерживает Microsoft.Долг.Безопасность.Facebook версии 3.0.1 больше нет. Для меня это сработало, чтобы установить версию 3.1.0. Для обновления до версии 3.1.0 выполните команду Install-Package Microsoft.Owin.Security.Facebook в консоли диспетчера пакетов:https://www.nuget.org/packages/Microsoft.Owin.Security.Facebook

Ок у меня есть решение проблемы.

это код, который я имел ранее в мой стартап.Автор.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);

обратите внимание, как

x.Scope.Add("email")

строка была закомментирована, но все же я запрашиваю электронную почту позже в обработчике OnAuthenticated? Да, это так. По какой-то причине это работало безупречно в течение нескольких недель.

мое решение состояло в том, чтобы просто раскомментировать X.Scope.Добавить ("электронная почта"); строка, чтобы убедиться, что область=электронная почта переменная присутствовала в первоначальном запросе к Facebook.

теперь все работает как было!

Я не могу понять, почему это работало раньше, как это было. Единственное объяснение, которое я могу придумать, это то, что Facebook изменил что-то на своем конце.

У меня была такая же проблема с аутентификацией Google. Для меня сработало следующее:изменения в Google OAuth 2.0 и обновления в Google middleware для 3.0.0 RC release

последнее обновление Facebook было на 2015-02-09 (https://www.nuget.org/packages/Microsoft.AspNet.WebPages.OAuth/)

последней версией API на тот момент была версия 2.2. Срок действия версии 2.2 истек 25 марта 2017 года, что совпало с началом проблемы. (https://developers.facebook.com/docs/apps/changelog)

Я предполагаю, что Facebook, вероятно, автоматически обновил API, и теперь библиотека MS OAUTH не может чтобы разобрать новый ответ.

tldr: библиотека OAuth веб-страниц Microsoft устарела (по крайней мере для FB), и вам, вероятно, придется найти другое решение

у меня тоже была эта проблема, но она не была вызвана настройкой области. Мне потребовалось много времени, чтобы понять это, но то, что наконец-то просветил по настройки регистратора, установив следующий в OwinStartup.Configuration(IAppBuilder app).

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

это выводило следующее:

2014-05-31 21:14:48,508 [8] Ошибка
Microsoft.Долг.Безопасность.Cookies.CookieAuthenticationMiddleware
[(null)] - 0x00000000 - ошибка аутентификации
Системы.Чистая.Протоколу HTTP.HttpRequestException: произошла ошибка при отправке запрос. --- >System. Net. WebException: удаленное имя не может
будьте решительны: 'graph.facebook.com-в
Системы.Чистая.Класса HttpWebRequest.Метода endgetresponse(объекта iasyncresult asyncresult, к)
в системе.Чистая.Протоколу HTTP.HttpClientHandler.GetResponseCallback(Объекта Iasyncresult Арканзас) --- конец внутреннее исключение трассировки стека - в
Система.Во время выполнения.CompilerServices.TaskAwaiter.ThrowForNonSuccess (Task
задача) в
Система.Во время выполнения.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Задача задача) в системе.Во время выполнения.CompilerServices.TaskAwaiter`1.Метод getresult() в
Microsoft.Долг.Безопасность.Фейсбук.FacebookAuthenticationHandler.Д__0.MoveNext ()

на основе вышеприведенного стека вызовов я обнаружил, что моя виртуальная машина Azure не смогла разрешить graph.facebook.com. все, что мне нужно было сделать, чтобы исправить это, было запустить "ipconfig /registerdns", и я все исправил...

вышеуказанные решения не работают для меня. В конце концов, это, казалось, было связано с сессией. При "пробуждении" сеанса в предыдущем вызове он больше не будет возвращать null из 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 }));
    }

Как и OP, у меня был 3rd party auth, работающий нормально в течение длительного времени, а затем внезапно он остановился. Я верю, что это произошло из-за изменений, внесенных в мой код, когда я настроил сеанс для использования кэша Redis в Azure.

Я работаю над решением в течение трех дней. И я только что нашел его на 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);

и получить значения

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

У меня тоже эта проблема. Я прочитал много статей и тем в течение двух дней, включая это. Никто из них не работал на меня. Вход в систему Facebook отлично работал до развертывания, но после этого этого не произошло. Я узнал, что что-то не так с моим подключением к интернету VPS, поэтому я попробовал NextVPN на моем VPS. Но когда я попытался войти через свой компьютер, не было никакого нового соединения во внутреннем ProxiFire NextVPN. Наконец, установка OpenVPN на моем VPS решила проблему.

проверьте, что вы получаете внешнее подключение к интернету из вашего приложения. Если нет, исправьте внешнее подключение к интернету. Моя проблема заключалась в том, что я использовал экземпляр EC2 AWS, который внезапно перестал подключаться к интернету. Мне потребовалось некоторое время, чтобы понять, в чем проблема.

это сводило меня с ума. Все работало, пока я не развернулся в своей промежуточной среде. Я использовал Microsoft.Долг.Безопасность.Facebook версии 3.0.1 от Nuget. Обновил его до предварительной версии 3.1.0 от Nuget, и я больше не получал ошибку отказа в доступе...

хоть я и сделал все что sammy34 сказал, это не сработало для меня. Я был в одной точке с HaukurHaf: когда я делаю apirequest вручную в браузере, он отлично работает, но если я использую свое приложение mvc,GetExternalLoginInfoAsync() всегда возвращает null.

поэтому я изменил некоторые строки на sammy34'коды как на этот комментарий: https://stackoverflow.com/a/43148543/7776015

заменить:

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

вместо:

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

и добавил эту строку в моем FacebookAuthenticationOptions:

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

и теперь он работает.(поля и что параметры необязательные)

примечание: Я не обновлял Microsoft.Owin.Security.Facebook