使用 Google 用户而非 Chrome 使用的用户登录 Chrome 分机

Login to Chrome extension with a Google user other than the one in use by Chrome

我有一个 Chrome 扩展,请求用户使用 chrome.identity.getAuthToken 路由登录。这工作正常,但是当您登录时,您只能使用您在 Chrome 中拥有帐户的用户。

客户希望能够使用不同的 Google 帐户登录;因此,他们希望能够使用 the.client@company.com 登录,而不是使用 Chrome 登录的帐户 the.client@gmail.com,这也是一个有效的 Google 帐户。

我可以用一个帐户登录 Chrome,用第二个帐户登录 Gmail,但我没有在扩展程序中选择选项。

这可能吗?

不要使用 chrome.identity.getAuthToken 验证用户,只需自己实现 OAuth 部分。

您可以使用库来帮助您,但我上次尝试最有用的库(Google API 客户端)无法在 Chrome 扩展上运行。

查看 Google OpenID Connect 文档了解更多信息。最后,您所要做的就是将用户重定向到 OAuth URL,使用您的扩展程序获取 Google 的答案(授权代码),然后将授权代码转换为访问令牌(这是一个简单的 POST 调用。

由于 Chrome 扩展无法重定向到 Web 服务器,因此可以使用 installed app 重定向 URI:urn:ietf:wg:oauth:2.0:oob。使用此 Google 将显示包含授权码的页面。

只需使用您的扩展程序在此页面中注入一些javascript代码以获取授权码,关闭HTML页面,执行POST调用以获取用户的电子邮件。

根据 David 的回答,我发现 chrome.identity(以及通用 browser.identity)API 现在提供了一个 chrome.identity.launchWebAuthFlow 方法,可用于启动OAuth 工作流程。以下示例 class 展示了如何使用它:

class OAuth {

    constructor(clientId) {
        this.tokens = [];
        this.redirectUrl = chrome.identity.getRedirectURL();
        this.clientId = clientId;
        this.scopes = [
            "https://www.googleapis.com/auth/gmail.modify",
            "https://www.googleapis.com/auth/gmail.compose",
            "https://www.googleapis.com/auth/gmail.send"
        ];
        this.validationBaseUrl = "https://www.googleapis.com/oauth2/v3/tokeninfo";
    }

    generateAuthUrl(email) {
        const params = {
            client_id: this.clientId,
            response_type: 'token',
            redirect_uri: encodeURIComponent(this.redirectUrl),
            scope: encodeURIComponent(this.scopes.join(' ')),
            login_hint: email
        };

        let url = 'https://accounts.google.com/o/oauth2/auth?';
        for (const p in params) {
            url += `${p}=${params[p]}&`;
        }
        return url;
    }


    extractAccessToken(redirectUri) {
        let m = redirectUri.match(/[#?](.*)/);
        if (!m || m.length < 1)
            return null;
        let params = new URLSearchParams(m[1].split("#")[0]);
        return params.get("access_token");
    }

    /**
    Validate the token contained in redirectURL.
    This follows essentially the process here:
    https://developers.google.com/identity/protocols/OAuth2UserAgent#tokeninfo-validation
    - make a GET request to the validation URL, including the access token
    - if the response is 200, and contains an "aud" property, and that property
    matches the clientID, then the response is valid
    - otherwise it is not valid

    Note that the Google page talks about an "audience" property, but in fact
    it seems to be "aud".
    */
    validate(redirectURL) {
        const accessToken = this.extractAccessToken(redirectURL);
        if (!accessToken) {
            throw "Authorization failure";
        }
        const validationURL = `${this.validationBaseUrl}?access_token=${accessToken}`;
        const validationRequest = new Request(validationURL, {
            method: "GET"
        });

        function checkResponse(response) {
            return new Promise((resolve, reject) => {
                if (response.status != 200) {
                    reject("Token validation error");
                }
                response.json().then((json) => {
                    if (json.aud && (json.aud === this.clientId)) {
                        resolve(accessToken);
                    } else {
                        reject("Token validation error");
                    }
                });
            });
        }

        return fetch(validationRequest).then(checkResponse.bind(this));
    }

    /**
    Authenticate and authorize using browser.identity.launchWebAuthFlow().
    If successful, this resolves with a redirectURL string that contains
    an access token.
    */
    authorize(email) {
        const that = this;
        return new Promise((resolve, reject) => {
            chrome.identity.launchWebAuthFlow({
                interactive: true,
                url: that.generateAuthUrl(email)
            }, function(responseUrl) {
                resolve(responseUrl);
            });
        });
    }

    getAccessToken(email) {
        if (!this.tokens[email]) {
            const token = await this.authorize(email).then(this.validate.bind(this));
            this.tokens[email] = token;
        }
        return this.tokens[email];
    }
}

免责声明:以上class基于开源sample code from Mozilla Developer Network.

用法:

const clientId = "YOUR-CLIENT-ID"; // follow link below to see how to get client id
const oauth = new OAuth();
const token = await oauth.getAccessToken("sample@gmail.com");

当然,您需要自己处理令牌的过期,即当您从 Google 的 API 收到 401 时,移除令牌并尝试再次授权。

A complete sample extension using Google's OAuth can be found here.