使用Chrome应用程序身份API能否获取ID令牌?

20

我无法从Chrome身份API (https://developer.chrome.com/apps/identity)获取用户的ID令牌 (https://developers.google.com/accounts/docs/CrossClientAuth)。

当清单文件中的OAuth部分如下所示时,我可以使用Chrome身份示例获取访问令牌:

"oauth2": {
  "client_id": "<chrome-app-client-id>.apps.googleusercontent.com",
  "scopes": ["https://www.googleapis.com/auth/plus.login"]
}

但是当我尝试以与在我的Android客户端上获取id_token相同的方式获取它时,出现了错误:

"OAuth2请求失败:服务响应错误:'invalid scope: {0}'"

现在清单部分如下:

"oauth2": {
  "client_id": "<chrome-app-client-id>.apps.googleusercontent.com",
  "scopes": ["audience:server:client_id:<app-engine-client-id>.apps.googleusercontent.com"]
}
在Android中,我通过将相同的范围字符串传递给android.gms.auth.GoogleAuthUtil.getToken()来获取id_token,但是我无法使用Chrome身份验证API使其正常工作。
是否可以使用Chrome应用程序标识API获取id_token? 如果不行,那么如何为我的Chrome应用程序获取id_token?
感谢您的帮助!

1
OAuth的术语非常混乱。在您的帖子中,您提到了“用户ID”,“客户端ID”,“ID令牌”和“ID”。您能否准确地说明您正在尝试获取的是什么?(我知道一些关于“client_id”的事情,“id”对我来说太泛泛了,而我不确定“user id”是什么。) - Marc Rochkind
同意,有几个术语需要保持分开。已编辑帖子以提高清晰度。client_id在帖子的第一个链接中提到,需要在id_token中指定受众。我认为很清楚,我想要一个可以在附加到https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=时进行验证的id_token。 - chris
你尝试过将 openid 添加到作用域中吗?这通常是将服务器置于 OpenID Connect 模式并发出 ID 令牌所需的提示。 - Pieter Ennes
3个回答

22

昨天我也遇到了同样的问题,后来找到了一个解决办法,现在分享一下,因为这不是那么明显。根据我的了解,Google没有提供直接且有文档支持的方法来做到这一点,但是你可以使用chrome.identity.launchWebAuthFlow()函数。

首先,你需要在Google控制台中创建一个Web应用程序凭证,并将以下URL添加为有效的Authorized redirect URIhttps://<EXTENSION_OR_APP_ID>.chromiumapp.org。这个URI不需要存在,Chrome只是会捕捉重定向到该URL并稍后调用你的回调函数。

manifest.json

{
  "manifest_version": 2,
  "name": "name",
  "description": "description",
  "version": "0.0.0.1",
  "background": {
    "scripts": ["background.js"]
  },
  "permissions": [
    "identity"
  ],
  "oauth2": {
    "client_id": "<CLIENT_ID>.apps.googleusercontent.com",
    "scopes": [
      "openid", "email", "profile"
    ]
  }
}

background.js:

// Using chrome.identity
var manifest = chrome.runtime.getManifest();

var clientId = encodeURIComponent(manifest.oauth2.client_id);
var scopes = encodeURIComponent(manifest.oauth2.scopes.join(' '));
var redirectUri = encodeURIComponent('https://' + chrome.runtime.id + '.chromiumapp.org');

var url = 'https://accounts.google.com/o/oauth2/auth' + 
          '?client_id=' + clientId + 
          '&response_type=id_token' + 
          '&access_type=offline' + 
          '&redirect_uri=' + redirectUri + 
          '&scope=' + scopes;

chrome.identity.launchWebAuthFlow(
    {
        'url': url, 
        'interactive':true
    }, 
    function(redirectedTo) {
        if (chrome.runtime.lastError) {
            // Example: Authorization page could not be loaded.
            console.log(chrome.runtime.lastError.message);
        }
        else {
            var response = redirectedTo.split('#', 2)[1];

            // Example: id_token=<YOUR_BELOVED_ID_TOKEN>&authuser=0&hd=<SOME.DOMAIN.PL>&session_state=<SESSION_SATE>&prompt=<PROMPT>
            console.log(response);
        }
    }
);

Google OAuth2 API(用于OpenID Connect)文档可以在此处找到:https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters

PS:如果您的清单中不需要oauth2部分,可以安全地省略它,并仅在代码中提供标识符和范围。

编辑: 对于那些感兴趣的人,您不需要身份验证API。您甚至可以使用选项卡API的小技巧访问令牌。代码有点长,但您具有更好的错误消息和控制权。请记住,在以下示例中,您需要创建 Chrome App 凭据。

manifest.json

{
  "manifest_version": 2,
  "name": "name",
  "description": "description",
  "version": "0.0.0.1",
  "background": {
    "scripts": ["background.js"]
  },
  "permissions": [
    "tabs"
  ],
  "oauth2": {
    "client_id": "<CLIENT_ID>.apps.googleusercontent.com",
    "scopes": [
      "openid", "email", "profile"
    ]
  }
}

background.js:

// Using chrome.tabs
var manifest = chrome.runtime.getManifest();

var clientId = encodeURIComponent(manifest.oauth2.client_id);
var scopes = encodeURIComponent(manifest.oauth2.scopes.join(' '));
var redirectUri = encodeURIComponent('urn:ietf:wg:oauth:2.0:oob:auto');

var url = 'https://accounts.google.com/o/oauth2/auth' + 
          '?client_id=' + clientId + 
          '&response_type=id_token' + 
          '&access_type=offline' + 
          '&redirect_uri=' + redirectUri + 
          '&scope=' + scopes;

var RESULT_PREFIX = ['Success', 'Denied', 'Error'];
chrome.tabs.create({'url': 'about:blank'}, function(authenticationTab) {
    chrome.tabs.onUpdated.addListener(function googleAuthorizationHook(tabId, changeInfo, tab) {
        if (tabId === authenticationTab.id) {
            var titleParts = tab.title.split(' ', 2);

            var result = titleParts[0];
            if (titleParts.length == 2 && RESULT_PREFIX.indexOf(result) >= 0) {
                chrome.tabs.onUpdated.removeListener(googleAuthorizationHook);
                chrome.tabs.remove(tabId);

                var response = titleParts[1];
                switch (result) {
                    case 'Success':
                        // Example: id_token=<YOUR_BELOVED_ID_TOKEN>&authuser=0&hd=<SOME.DOMAIN.PL>&session_state=<SESSION_SATE>&prompt=<PROMPT>
                        console.log(response);
                    break;
                    case 'Denied':
                        // Example: error_subtype=access_denied&error=immediate_failed
                        console.log(response);
                    break;
                    case 'Error':
                        // Example: 400 (OAuth2 Error)!!1
                        console.log(response);
                    break;
                }
            }
        }
    });

    chrome.tabs.update(authenticationTab.id, {'url': url});
});

嗨Piotr,我尝试了你的解决方案,但是我遇到了“无法加载授权页面”的错误。我按照你的步骤进行操作,清单中的范围也相同。 - Egidio Caprino
嗨@Aegidius,你解决了这个问题吗?我也遇到了同样的问题。谢谢。 - rsb
3
需要进行两处更改。1)新的终端基础是 https://accounts.google.com/o/oauth2/v2/auth。2)在认证 URL 中,nonce 是必需的 URL 参数,因此请添加类似于 &nonce=<你的 nonce 值> 的内容。 - dadykhoff
1
推荐使用 id_token 在扩展程序和后端服务器之间传递用户信息(详见:https://developers.google.com/identity/sign-in/web/backend-auth)。但是,我无论如何都无法使其正常工作,因为在尝试从凭据控制台创建 OAuth Web 客户端时,会收到“无效重定向:在提交之前必须将域添加到授权域列表中”的消息。将扩展程序的 URL 添加到授权域不起作用,因为这些域名“必须是顶级私有域”。看起来像个进退两难的局面……不知道该怎么办。 - chas
背景页面打开新标签页成功了,谢谢!似乎我的 page_action 无法将自己重定向到登录页面。 - neaumusic
显示剩余3条评论

0

首先,我假设在您的manifest.json片段中,您并不是真的意思是"<chrome-app-client-id>.apps.googleusercontent.com。它应该是像9414861317621.apps.googleusercontent.com这样的东西——您从开发者控制台或任何您用来注册应用程序的Google网站上获得的。

假设以上都没问题,而且您已经正确设置了client_id和scope,您可以通过调用chrome.identity.getAuthToken来获取所谓的“OAuth2访问令牌”。由于您没有向我们展示任何JavaScript代码,我无法确定这是否是您正在执行的操作。您获得的访问令牌需要保存以供后续使用,当您调用API函数时就会用到。例如:

var access_token;

chrome.identity.getAuthToken(
    {
        'interactive': true
    },
    function(token) {
        access_token = token;
        // do something if you like to indicate
        // that the app is authorized
    }
);

然后,当您进行API调用时,您会提供该访问令牌,就像这样:

var url = 'https://www.googleapis.com/' + method;
Ajax.ajaxSend(url, "json",
    function (status, response) {
        if (response && response.error && response.error.message)
            errorCallback(response.error.message);
        else if (status == 200)
            successCallback(response);
        else
            errorCallback('Result code: ' + status);
    },
    function (e) {
        if (errorCallback)
            errorCallback('Communication error');
    },
    {
        Authorization: 'Bearer ' + access_token
    }
);

Ajax.ajaxSend 是我自己的函数:

var Ajax = (function () {
    var api = {
        ajaxSend: function (url, responseType, successCallback, errorCallback, headers) {
            var req = new XMLHttpRequest();
            req.onload = function (e) {
                successCallback(req.status, req.response);
            };
            req.onerror = errorCallback;
            req.responseType = responseType ? responseType : "text";
            req.open("get", url);
            if (headers)
                for (var v in headers)
                    req.setRequestHeader(v, headers[v]);
            req.send();
        }
    };
    return api;
})();

其他未定义的函数也是你期望的那样。`Ajax.ajaxSend` 的第三个参数是要发送的标头。(抱歉,我没有时间为这个答案开发独立的代码。)
希望以上内容对你有所帮助。

2
谢谢你的帮助,但这并不是我问题的答案。在我的问题的第3行中,我已经说了我知道如何获取access_token(我包含这个信息是为了避免回答者解释如何获取它)。我想要获取的是id_token(一个可以在此URL后面附加进行验证的令牌:"googleapis.com/oauth2/v1/tokeninfo?id_token=")。 - chris
1
你最终解决了这个问题吗?我也在尝试做同样的事情。谢谢。 - rsb
1
仍在努力解决这个问题 :/ - kidCoder

0

我猜这取决于为什么需要令牌ID,但在我的情况下,access_token足以授权用户 - 通过从https://www.googleapis.com/oauth2/v2/userinfo?alt=json获取用户信息(使用Authorization头=access_token)。


1
id_token 可以传递给服务器,表示“是的,我就是这个用户——谷歌说我是,而且我打算将此批准用于您的应用程序(项目 client_id),您可以使用谷歌的公共 .well-known 证书验证该声明”。 - neaumusic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接