Google 数据 API 授权重定向 URI 不匹配 [英] Google Data API Authorization Redirect URI Mismatch

查看:27
本文介绍了Google 数据 API 授权重定向 URI 不匹配的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景

我想在 .NET Core 1.1 中编写一个小型的个人 Web 应用程序来与 YouTube 交互并使我更容易做一些事情,我正在关注

解决方案

原始答案有效,但它不是 ASP.NET Web 应用程序的最佳方法.有关处理 ASP.NET Web 应用程序流的更好方法,请参阅下面的更新.

<小时>

原答案

所以,我想通了.问题在于 Google 将 Web 应用程序视为基于 JavaScript 的 Web 应用程序,而不是具有服务器端处理功能的 Web 应用程序.因此,您不能在 Google Developer Console 中为基于服务器的 Web 应用程序创建 Web 应用程序 OAuth 客户端 ID.

解决方法是在 Google Developer Console 中创建 OAuth Client ID 时选择类型Other.这将使 Google 将其视为已安装的应用程序而不是 JavaScript 应用程序,因此不需要重定向 URI 来处理回调.

这有点令人困惑,因为 Google 的 .NET 文档告诉您创建 Web 应用 OAuth 客户端 ID.

<小时>

2018 年 2 月 16 日更新了更好的答案:

我想提供对此答案的更新.虽然,我上面所说的有效,但这并不是为 ASP.NET 解决方案实现 OAuth 工作流的最佳方式.有一种更好的方法可以实际使用适当的 OAuth 2.0 流程.Google 的文档在这方面很糟糕(尤其是对于 .NET),所以我将在这里提供一个简单的实现示例.该示例使用 ASP.NET 核心,但它很容易适应完整的 .NET 框架:)

注意:Google 确实有一个 Google.Apis.Auth.MVC 包来帮助简化此 OAuth 2.0 流程,但不幸的是,它与特定的 MVC 实现耦合,不适用于 ASP.NET Core或 Web API.所以,我不会使用它.我将给出的示例适用于所有 ASP.NET 应用程序.相同的代码流可用于您启用的任何 Google API,因为它取决于您请求的范围.

另外,我假设您已在 Google 开发者仪表板中设置了您的应用程序.也就是说,您已经创建了一个应用程序,启用了必要的 YouTube API,创建了一个 Web 应用程序客户端,并正确设置了您允许的重定向 url.

流程将像这样工作:

  1. 用户点击按钮(例如添加 YouTube)
  2. View调用Controller上的方法获取授权URL
  3. 在控制器方法中,我们要求 Google 根据我们的客户端凭据(在 Google Developer Dashboard 中创建的凭据)向我们提供授权 URL,并向 Google 提供我们应用程序的重定向 URL(此重定向 URL 必须在您的您的 Google 应用程序接受的重定向 URL 列表)
  4. Google 将授权 URL 返还给我们
  5. 我们将用户重定向到该授权 URL
  6. 用户授予我们的应用访问权限
  7. Google 使用我们根据请求提供给 Google 的重定向 URL 向我们的应用程序返回一个特殊的访问代码
  8. 我们使用该访问代码为用户获取 Oauth 令牌
  9. 我们为用户保存 Oauth 令牌

您需要以下 NuGet 包

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

模型

公共类 ExampleModel{公共布尔 UserHasYoutubeToken { 获取;放;}}

控制者

公共类 ExampleController : 控制器{//我假设您有某种服务可以从您的数据库中读取用户并将用户更新到您的数据库私有 IUserService 用户服务;公共示例控制器(IUserService 用户服务){this.userService = 用户服务;}公共异步任务<IActionResult>指数(){var userId =//获取你的用户 ID//我假设你有办法知道用户是否有 YouTube 的访问令牌var userHasToken = this.userService.UserHasYoutubeToken(userId);var 模型 = 新 ExampleModel { UserHasYoutubeToken = userHasToken }返回视图(模型);}//这是我们将用来获取授权码流的方法私有 AuthorizationCodeFlow GetGoogleAuthorizationCodeFlow(参数字符串 [] 范围){var clientIdPath = @"C:PathToMyclient_id.json";使用 (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);返回 googleAuthorizationCodeFlow;}}//这是您的视图将调用的路由(我们将使用 JQuery 调用它)[HttpPost]公共异步任务<字符串>GetAuthorizationUrl(){//首先,我们需要构建一个重定向 URL,在用户授予访问权限后,Google 将使用该 URL 重定向回应用程序var 协议 = Request.IsHttps ?https":http";var redirectUrl = $"{protocol}://{Request.Host}/{Url.Action(nameof(this.GetYoutubeAuthenticationToken)).TrimStart('/')}";//接下来,让我们定义我们将要访问的范围.我们正在请求 YouTubeForceSsl,以便我们可以管理用户的 YouTube 帐户.var scopes = new[] { YouTubeService.Scope.YoutubeForceSsl };//现在,让我们获取 AuthorizationCodeFlow,它将生成一个唯一的授权 URL 以将我们的用户重定向到var googleAuthorizationCodeFlow = this.GetGoogleAuthorizationCodeFlow(scopes);var codeRequestUrl = googleAuthorizationCodeFlow.CreateAuthorizationCodeRequest(redirectUrl);codeRequestUrl.ResponseType = "代码";//构建网址var authorizationUrl = codeRequestUrl.Build();//将其返回给我们的调用者进行重定向返回授权网址;}公共异步任务<IActionResult>GetYoutubeAuthenticationToken([FromQuery] 字符串代码){如果(字符串.IsNullOrEmpty(代码)){/*这意味着用户取消了并且没有授予我们访问权限.在这种情况下,会有一个查询参数在名为错误"的请求 URL 上,该 URL 将包含错误消息.但是,您可以处理这种情况.在这里,我们不会做任何事情,但是您应该编写代码来处理这种情况,但是您的应用程序需要.*/}//userId 是与您的应用程序相关的用户 ID(不是他们的 Youtube ID).//这是您在他们注册时分配给他们的用户 ID,或者您唯一标识使用您的应用程序的人var userId =//获取您的用户 ID(无论是在声明中还是存储在会话中或其他地方)//我们需要再次构建相同的重定向 url.我认为谷歌使用它进行验证......?不确定它是做什么用的//在这个阶段,我只知道我们需要它 :)var 协议 = Request.IsHttps ?https":http";var redirectUrl = $"{protocol}://{Request.Host}/{Url.Action(nameof(this.GetYoutubeAuthenticationToken)).TrimStart('/')}";//现在,让我们向 Youtube 请求我们的 OAuth 令牌,它可以让我们为用户做一些很棒的事情var scopes = new[] { YouTubeService.Scope.YoutubeForceSsl };var googleAuthorizationCodeFlow = this.GetYoutubeAuthorizationCodeFlow(scopes);var token = await googleAuthorizationCodeFlow.ExchangeCodeForTokenAsync(userId, code, redirectUrl, CancellationToken.None);//现在,您需要将此令牌存储给您的用户.因此,无论您如何保存用户数据,只要确保您//为您的用户保存令牌.这是您将用来建立代表行事所需的 UserCredentials 的令牌//用户的.var tokenJson = JsonConvert.SerializeObject(token);等待 this.userService.SaveUserToken(userId, tokenJson);//现在我们已经可以访问用户的 YouTube 帐户,让我们返回//到我们的应用程序 :)return RedirectToAction(nameof(this.Index));}}

观点

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

@if(Model.UserHasYoutubeToken){<p>耶!我们可以访问您的 YouTube 帐户!</p>}别的{<button id="addYoutube">添加 YouTube</button>}</div><脚本>$(文档).ready(函数 () {var addYoutubeUrl = '@Url.Action(nameof(ExampleController.GetAuthorizationUrl))';//当用户点击添加 YouTube"按钮时,我们将调用服务器//获取 Google 为我们构建的授权 URL,然后重定向//用户到它.$('#addYoutube').click(function () {$.post(addYoutubeUrl, 函数(结果){如果(结果){window.location.href = 结果;}});});});</脚本>

Background

I am wanting to write a small, personal web app in .NET Core 1.1 to interact with YouTube and make some things easier for me to do and I am following the tutorials/samples in Google's YouTube documentation. Sounds simple enough, right? ;)

Authenticating with Google's APIs seems impossible! I have done the following:

  1. Created an account in the Google Developer Console
  2. Created a new project in the Google Developer Console
  3. Created a Web Application OAuth Client ID and added my Web App debug URI to the list of approved redirect URIs
  4. Saved the json file provided after generating the OAuth Client ID to my system
  5. In my application, my debug server url is set (and when my application launches in debug, it's using the url I set which is http://127.0.0.1:60077).

However, when I attempt to authenticate with Google's APIs, I recieve the following error:

  1. That’s an error.

Error: redirect_uri_mismatch

The redirect URI in the request, http://127.0.0.1:63354/authorize/, does not match the ones authorized for the OAuth client.

Problem

So now, for the problem. The only thing I can find when searching for a solution for this is people that say

just put the redirect URI in your approved redirect URIs

Unfortunately, the issue is that every single time my code attempts to authenticate with Google's APIs, the redirect URI it is using changes (the port changes even though I set a static port in the project's properties). I cannot seem to find a way to get it to use a static port. Any help or information would be awesome!

NOTE: Please don't say things like "why don't you just do it this other way that doesn't answer your question at all".

The code

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/"
        ]
    }
}

Method That Is Attempting to Use API

public async Task<IActionResult> Test()
{
    string ClientIdPath = @"C:PathToMyclient_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);
}

Project Properties

解决方案

The Original Answer works, but it is NOT the best way to do this for an ASP.NET Web Application. See the update below for a better way to handle the flow for an ASP.NET Web Application.


Original Answer

So, I figured this out. The issue is that Google thinks of a web app as a JavaScript based web application and NOT a web app with server side processing. Thus, you CANNOT create a Web Application OAuth Client ID in the Google Developer Console for a server based web application.

The solution is to select the type Other when creating an OAuth Client ID in the Google Developer Console. This will have Google treat it as an installed application and NOT a JavaScript application, thus not requiring a redirect URI to handle the callback.

It's somewhat confusing as Google's documentation for .NET tells you to create a Web App OAuth Client ID.


Feb 16, 2018 Updated Better Answer:

I wanted to provide an update to this answer. Though, what I said above works, this is NOT the best way to implement the OAuth workflow for a ASP.NET solution. There is a better way which actually uses a proper OAuth 2.0 flow. Google's documentation is terrible in regards to this (especially for .NET), so I'll provide a simple implementation example here. The sample is using ASP.NET core, but it's easily adapted to the full .NET framework :)

Note: Google does have a Google.Apis.Auth.MVC package to help simplifiy this OAuth 2.0 flow, but unfortunately it's coupled to a specific MVC implementation and does not work for ASP.NET Core or Web API. So, I wouldn't use it. The example I'll be giving will work for ALL ASP.NET applications. This same code flow can be used for any of the Google APIs you've enabled as it's dependent on the scopes you are requesting.

Also, I am assuming you have your application set up in your Google Developer dashboard. That is to say that you have created an application, enabled the necessary YouTube APIs, created a Web Application Client, and set your allowed redirect urls properly.

The flow will work like this:

  1. The user clicks a button (e.g. Add YouTube)
  2. The View calls a method on the Controller to obtain an Authorization URL
  3. On the controller method, we ask Google to give us an Authorization URL based on our client credentials (the ones created in the Google Developer Dashboard) and provide Google with a Redirect URL for our application (this Redirect URL must be in your list of accepted Redirect URLs for your Google Application)
  4. Google gives us back an Authorization URL
  5. We redirect the user to that Authorization URL
  6. User grants our application access
  7. Google gives our application back a special access code using the Redirect URL we provided Google on the request
  8. We use that access code to get the Oauth tokens for the user
  9. We save the Oauth tokens for the user

You need the following NuGet Packages

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

The Model

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

The Controller

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:PathToMyclient_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));
    }
}

The View

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

这篇关于Google 数据 API 授权重定向 URI 不匹配的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆