Несоответствие URI перенаправления авторизации API данных Google

Фон

Я хочу написать небольшое персональное веб-приложение на .NET Core 1.1 для взаимодействия с YouTube и упростить мне некоторые вещи, и я следую руководствам / примерам в Документация Google по YouTube. Звучит достаточно просто, правда? ;)

Аутентификация с помощью API Google кажется невозможной! Я сделал следующее:

  1. Создал аккаунт в консоли разработчика Google
  2. Создал новый проект в консоли разработчика Google
  3. Создал идентификатор клиента OAuth веб-приложения и добавил свой URI отладки веб-приложения в список утвержденных URI перенаправления.
  4. Сохранен файл json, предоставленный после создания идентификатора клиента OAuth в моей системе.
  5. В моем приложении установлен URL-адрес сервера отладки (и когда мое приложение запускается в режиме отладки, оно использует заданный мной URL-адрес http://127.0.0.1:60077).

Однако, когда я пытаюсь пройти аутентификацию с помощью API Google, я получаю следующую ошибку:

  1. Это ошибка.

Ошибка: redirect_uri_mismatch

URI перенаправления в запросе, http://127.0.0.1:63354/authorize/, не совпадают с авторизованными для клиента OAuth.

Проблема

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

просто поместите URI перенаправления в свои утвержденные URI перенаправления

К сожалению, проблема заключается в том, что каждый раз, когда мой код пытается аутентифицироваться с помощью API Google, используемый им URI перенаправления изменяется (порт изменяется, хотя я установил статический порт в свойствах проекта). Кажется, я не могу найти способ заставить его использовать статический порт. Любая помощь или информация были бы потрясающими!

ПРИМЕЧАНИЕ. Пожалуйста, не говорите таких вещей, как «почему бы вам просто не сделать это иначе, чтобы вообще не ответить на ваш вопрос».

Код

client_id.json

{
    "web": {
        "client_id": "[MY_CLIENT_ID]",
        "project_id": "[MY_PROJECT_ID]",
        "auth_uri": "https://accounts.google.com/o/oauth2/auth",
        "token_uri": "https://accounts.google.com/o/oauth2/token",
        "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
        "client_secret": "[MY_CLIENT_SECRET]",
        "redirect_uris": [
            "http://127.0.0.1:60077/authorize/"
        ]
    }
}

Метод, пытающийся использовать API

public async Task<IActionResult> Test()
{
    string ClientIdPath = @"C:\Path\To\My\client_id.json";
    UserCredential credential;

    using (var stream = new FileStream(ClientIdPath, FileMode.Open, FileAccess.Read))
    {
        credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
            GoogleClientSecrets.Load(stream).Secrets,
            new[] { YouTubeService.Scope.YoutubeReadonly },
            "user",
            CancellationToken.None,
            new FileDataStore(this.GetType().ToString())
        );
    }

    var youtubeService = new YouTubeService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = credential,
        ApplicationName = this.GetType().ToString()
    });

    var channelsListRequest = youtubeService.Channels.List("contentDetails");
    channelsListRequest.Mine = true;

    // Retrieve the contentDetails part of the channel resource for the authenticated user's channel.
    var channelsListResponse = await channelsListRequest.ExecuteAsync();

    return Ok(channelsListResponse);
}

Свойства проекта

Свойства проекта


person Stephen P.    schedule 24.06.2017    source источник


Ответы (4)


Исходный ответ работает, но это НЕ лучший способ сделать это для веб-приложения ASP.NET. См. Обновление ниже, чтобы лучше справиться с потоком для веб-приложения ASP.NET.


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

Итак, я понял это. Проблема в том, что Google считает веб-приложение веб-приложением на основе JavaScript, а НЕ веб-приложением с обработкой на стороне сервера. Таким образом, вы НЕ МОЖЕТЕ создать идентификатор клиента OAuth веб-приложения в консоли разработчика Google для серверного веб-приложения.

Решение состоит в том, чтобы выбрать тип Другое при создании идентификатора клиента OAuth в консоли разработчика Google. Это заставит Google рассматривать его как установленное приложение, а НЕ приложение JavaScript, таким образом, не требуя URI перенаправления для обработки обратного вызова.

Это несколько сбивает с толку, поскольку в документации Google для .NET говорится о создании идентификатора клиента OAuth для веб-приложения.


16 февраля 2018 г. Обновленный лучший ответ:

Я хотел обновить этот ответ. Хотя то, что я сказал выше, работает, это НЕ лучший способ реализовать рабочий процесс OAuth для решения ASP.NET. Есть лучший способ, который фактически использует правильный поток OAuth 2.0. Документация Google ужасна в этом отношении (особенно для .NET), поэтому я приведу здесь простой пример реализации. В образце используется ядро ​​ASP.NET, но его легко адаптировать к полной платформе .NET :)

Примечание. У Google есть пакет Google.Apis.Auth.MVC, чтобы упростить этот поток OAuth 2.0, но, к сожалению, он связан с конкретной реализацией MVC и не работает для ASP.NET Core или веб-API. . Так что я бы не стал его использовать. Пример, который я приведу, будет работать для ВСЕХ приложений ASP.NET. Этот же поток кода можно использовать для любого из включенных вами API Google, поскольку он зависит от запрашиваемых вами областей.

Кроме того, я предполагаю, что ваше приложение настроено на панели инструментов разработчика Google. То есть вы создали приложение, включили необходимые API YouTube, создали клиент веб-приложения и правильно настроили разрешенные URL-адреса для перенаправления.

Поток будет работать так:

  1. Пользователь нажимает кнопку (например, Добавить YouTube)
  2. View вызывает метод контроллера для получения URL-адреса авторизации.
  3. В методе контроллера мы просим Google предоставить нам URL-адрес авторизации на основе наших учетных данных клиента (тех, которые созданы в панели инструментов разработчика Google) и предоставить Google URL-адрес перенаправления для нашего приложения (этот URL-адрес перенаправления должен быть в вашем списке принятых URL-адреса перенаправления для вашего приложения Google)
  4. Google возвращает нам URL-адрес авторизации
  5. Мы перенаправляем пользователя на этот URL-адрес авторизации
  6. Пользователь предоставляет нашему приложению доступ
  7. Google возвращает нашему приложению специальный код доступа, используя URL-адрес перенаправления, который мы предоставили Google по запросу.
  8. Мы используем этот код доступа, чтобы получить токены Oauth для пользователя.
  9. Сохраняем токены Oauth для пользователя

Вам понадобятся следующие пакеты NuGet

  1. Google.Apis
  2. Google.Apis.Auth
  3. Google.Apis.Core
  4. Google.apis.YouTube.v3

Модель

public class ExampleModel
{
    public bool UserHasYoutubeToken { get; set; }
}

Контроллер

public class ExampleController : Controller
{
    // I'm assuming you have some sort of service that can read users from and update users to your database
    private IUserService userService;

    public ExampleController(IUserService userService)
    {
        this.userService = userService;
    }

    public async Task<IActionResult> Index()
    {
        var userId = // Get your user's ID however you get it

        // I'm assuming you have some way of knowing if a user has an access token for YouTube or not
        var userHasToken = this.userService.UserHasYoutubeToken(userId);

        var model = new ExampleModel { UserHasYoutubeToken = userHasToken }
        return View(model);
    }

    // This is a method we'll use to obtain the authorization code flow
    private AuthorizationCodeFlow GetGoogleAuthorizationCodeFlow(params string[] scopes)
    {
        var clientIdPath = @"C:\Path\To\My\client_id.json";
        using (var fileStream = new FileStream(clientIdPath, FileMode.Open, FileAccess.Read))
        {
            var clientSecrets = GoogleClientSecrets.Load(stream).Secrets;
            var initializer = new GoogleAuthorizationCodeFlow.Initializer { ClientSecrets = clientSecrets, Scopes = scopes };
            var googleAuthorizationCodeFlow = new GoogleAuthorizationCodeFlow(initializer);

            return googleAuthorizationCodeFlow;
        }
    }

    // This is a route that your View will call (we'll call it using JQuery)
    [HttpPost]
    public async Task<string> GetAuthorizationUrl()
    {
        // First, we need to build a redirect url that Google will use to redirect back to the application after the user grants access
        var protocol = Request.IsHttps ? "https" : "http";
        var redirectUrl = $"{protocol}://{Request.Host}/{Url.Action(nameof(this.GetYoutubeAuthenticationToken)).TrimStart('/')}";

        // Next, let's define the scopes we'll be accessing. We are requesting YouTubeForceSsl so we can manage a user's YouTube account.
        var scopes = new[] { YouTubeService.Scope.YoutubeForceSsl };

        // Now, let's grab the AuthorizationCodeFlow that will generate a unique authorization URL to redirect our user to
        var googleAuthorizationCodeFlow = this.GetGoogleAuthorizationCodeFlow(scopes);
        var codeRequestUrl = googleAuthorizationCodeFlow.CreateAuthorizationCodeRequest(redirectUrl);
        codeRequestUrl.ResponseType = "code";

        // Build the url
        var authorizationUrl = codeRequestUrl.Build();

        // Give it back to our caller for the redirect
        return authorizationUrl;
    }

    public async Task<IActionResult> GetYoutubeAuthenticationToken([FromQuery] string code)
    {
        if(string.IsNullOrEmpty(code))
        {
            /* 
                This means the user canceled and did not grant us access. In this case, there will be a query parameter
                on the request URL called 'error' that will have the error message. You can handle this case however.
                Here, we'll just not do anything, but you should write code to handle this case however your application
                needs to.
            */
        }

        // The userId is the ID of the user as it relates to YOUR application (NOT their Youtube Id).
        // This is the User ID that you assigned them whenever they signed up or however you uniquely identify people using your application
        var userId = // Get your user's ID however you do (whether it's on a claim or you have it stored in session or somewhere else)

        // We need to build the same redirect url again. Google uses this for validaiton I think...? Not sure what it's used for
        // at this stage, I just know we need it :)
        var protocol = Request.IsHttps ? "https" : "http";
        var redirectUrl = $"{protocol}://{Request.Host}/{Url.Action(nameof(this.GetYoutubeAuthenticationToken)).TrimStart('/')}";

        // Now, let's ask Youtube for our OAuth token that will let us do awesome things for the user
        var scopes = new[] { YouTubeService.Scope.YoutubeForceSsl };
        var googleAuthorizationCodeFlow = this.GetYoutubeAuthorizationCodeFlow(scopes);
        var token = await googleAuthorizationCodeFlow.ExchangeCodeForTokenAsync(userId, code, redirectUrl, CancellationToken.None);

        // Now, you need to store this token in rlation to your user. So, however you save your user data, just make sure you
        // save the token for your user. This is the token you'll use to build up the UserCredentials needed to act on behalf
        // of the user.
        var tokenJson = JsonConvert.SerializeObject(token);
        await this.userService.SaveUserToken(userId, tokenJson);

        // Now that we've got access to the user's YouTube account, let's get back
        // to our application :)
        return RedirectToAction(nameof(this.Index));
    }
}

Обзор

@using YourApplication.Controllers
@model YourApplication.Models.ExampleModel

<div>
    @if(Model.UserHasYoutubeToken)
    {
        <p>YAY! We have access to your YouTube account!</p>
    }
    else
    {
        <button id="addYoutube">Add YouTube</button>
    }
</div>

<script>
    $(document).ready(function () {
        var addYoutubeUrl = '@Url.Action(nameof(ExampleController.GetAuthorizationUrl))';

        // When the user clicks the 'Add YouTube' button, we'll call the server
        // to get the Authorization URL Google built for us, then redirect the
        // user to it.
        $('#addYoutube').click(function () {
            $.post(addYoutubeUrl, function (result) {
                if (result) {
                    window.location.href = result;
                }
            });
        });
    });
</script>
person Stephen P.    schedule 26.06.2017
comment
Когда я использую учетные данные типа «Другое», меня все еще перенаправляют на случайный порт. Запуск веб-сервера django. - person Dan Walters; 11.05.2020

Как указано здесь, вам необходимо указать порт исправления для сервера разработки ASP.NET, например Как исправить номер порта в разработке asp.NET server и добавьте этот URL-адрес с портом исправления к разрешенным URL-адресам. Также, как указано в этом thread, когда ваш браузер перенаправляет пользователя на страницу oAuth Google, вы должны передать в качестве параметра URI перенаправления, на который сервер Google должен возвращаться с ответом токена.

person abielita    schedule 25.06.2017
comment
Две вещи: Во-первых, мой порт исправлен, пожалуйста, посмотрите мои свойства (снимок экрана в конце моего сообщения). Во-вторых, я использую API данных Google, а не простой вызов API, поэтому не я устанавливаю URI перенаправления для вызова OAuth, а API Google. Я бы предпочел не собирать вместе комбинацию вызовов API «голого железа» и вызовов библиотек. - person Stephen P.; 26.06.2017

Я заметил, что есть простой способ, не связанный с программированием.

Если у вас есть типичное приложение monotlith, построенное в соответствии с типичным соглашением MS (поэтому оно несовместимо с 12-факторным и типичным DDD), есть возможность указать вашему прокси-серверу WWW переписать все запросы с HTTP на HTTPS, поэтому, даже если вы настроили веб-приложение на http://localhost:5000, а затем добавлен в URL-адрес API Google, например: http://your.domain.net/sigin-google, он будет работать отлично, и это не так, потому что гораздо безопаснее установить вверх по основной WWW переписать все на HTTPS.

Думаю, это не очень хорошая практика, но она имеет смысл и работает.

person obsidiam    schedule 17.09.2018
comment
mylive.stagingapplication.com начинается с https, но затем выдает ошибку 400 и при переходе на http не может обработать ExternalLoginCallback и выдает ошибку 500. Он отлично работает с localhost: 4220 / signin-google, но дает по моему живому URL - person Hina Khuman; 18.09.2018

Я часами боролся с этой проблемой в приложении .net Core. Что в конечном итоге исправило для меня, так это создание в консоли разработчиков Google учетных данных для «Настольного приложения» вместо «Веб-приложения».

введите здесь описание изображения  введите описание изображения здесь

person mack    schedule 16.06.2020