Аутентификация ASP.NET MVC5 OWIN Facebook внезапно не работает

Обновление 2017!

Проблема, с которой я столкнулась, когда я разместил оригинальный вопрос, не имеет ничего общего с недавними изменениями, сделанными Facebook, когда они заставили всех до версии 2.3 их API. Для решения этой конкретной проблемы см. ответы sammy34 ниже. Версия 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 получать доступ к нашему профилю. Когда мы нажимаем "ОК", мы перенаправляемся обратно на наш сайт, но вместо входа в систему мы просто оказываемся на экране входа в систему.

Я прошел этот процесс в режиме отладки, и у меня есть этот 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 явно что-то не так.

Ответ 1

Хорошо У меня есть решение проблемы.

Это код, который у меня был ранее в файле 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);

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

x.Scope.Add("email")
Строка

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

Мое решение состояло в том, чтобы просто раскомментировать x.Scope.Add( "email" ); чтобы убедиться, что переменная scope = email присутствует в первоначальном запросе на Facebook.

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

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

Ответ 2

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

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

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

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

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

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

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, содержащий эквивалентную информацию, закодированную в форме, из ответа JSON от Facebook. Обратите внимание, что этот код использует популярный пакет Json.Net.

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

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

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

Ответ 3

Заметила эту проблему вчера. Facebook больше не поддерживает Microsoft.Owin.Security.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

Ответ 5

Последнее обновление 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 не может проанализировать новый ответ.

TL;DR: Библиотека OAuth Microsoft WebPages устарела (по крайней мере для FB), и вам, вероятно, придется найти другое решение

Ответ 6

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

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

Это привело к следующему:

2014-05-31 21:14: 48,508 [8] ОШИБКА
Microsoft.Owin.Security.Cookies.CookieAuthenticationMiddleware
[(null)] - 0x00000000 - Ошибка аутентификации

System.Net.Http.HttpRequestException: произошла ошибка при отправке запрос. --- > System.Net.WebException: удаленное имя не может разрешить: "graph.facebook.com" на
System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
в System.Net.Http.HttpClientHandler.GetResponseCallback(IAsyncResult ar) --- Конец внутреннего стека стека исключений --- на
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task
задача) на
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task задача) в System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() на авто Microsoft.Owin.Security.Facebook.FacebookAuthenticationHandler.d__0.MoveNext()

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

Ответ 7

Вышеупомянутые решения не сработали для меня. В конце концов, похоже, это связано с сессией. "Пробуждая" сеанс в предыдущем вызове, он больше не будет возвращать 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, у меня был сторонний аут, работающий отлично в течение долгого времени, а затем внезапно прекратился. Я верю, что это произошло из-за изменений, внесенных в мой код, когда я настроил сеанс для использования Redis Cache на Azure.

Ответ 8

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

Ответ 9

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

Ответ 10

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

Ответ 11

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

Ответ 12

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

Итак, я изменил несколько строк на код sammy34, например, на этот комментарий: fooobar.com/info/89088/...

Заменены:

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