使用与Chrome浏览器不同的Google用户登录Chrome扩展程序

12

我有一个 Chrome 扩展程序,它使用 chrome.identity.getAuthToken 路由请求用户登录。这个功能很好用,但是当你登录时,你只能使用 Chrome 中拥有账户的用户。

客户希望能够使用其他 Google 帐户登录;因此,他们想要使用 .client@company.com 来登录,而不是使用已登录 Chrome 的帐户 .client@gmail.com 。

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

这个可能吗?

2个回答

16

不要使用 chrome.identity.getAuthToken 来验证用户身份,而是自己实现OAuth部分。

您可以使用库来帮助您,但上次我尝试时最有用的库(Google API Client)无法在Chrome扩展中工作。

查看Google OpenID Connect文档以获取更多信息。最后,你只需要将用户重定向到OAuth URL,使用你的扩展程序获取谷歌的答复(授权码),然后将授权码转换为访问令牌(这是一个简单的POST调用)。

由于对于Chrome扩展程序,您无法重定向到Web服务器,所以您可以使用安装应用程序重定向URI:urn:ietf:wg:oauth:2.0:oob。这样,Google将显示包含授权码的页面。

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


很棒的答案。这非常接近我们最终采取的方式。不过我希望我们当时知道已安装应用程序的重定向URI。我们采取的路线是直接跳转到OAuth批准页面,然后跳转到我们服务器上的一个页面,告知用户成功或失败的原因。有点迂回,但它能够正常工作,客户也很满意。 - boodle
1
如果您只需要用户的电子邮件/姓名,则此技术适用。如果您需要连接Chrome Webstore的许可证API,则必须在开发人员控制台中创建一个“Chrome应用程序”,该应用程序仅能使用chrome.identity.getAuthToken。 - Silver Moon
3
你能解释一下这部分内容吗:"只需使用你的扩展程序在此页面中注入一些JavaScript代码以获取授权码,关闭HTML页面,执行POST调用以获取用户的电子邮件。"? 我不太确定如何在用户完成流程后挂接到授权页面。 - Cory

1

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

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];
    }
}

免责声明: 上述课程基于开源 Mozilla 开发者网络的示例代码

用法:

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");

当然,您需要自己处理令牌的过期,即当您从谷歌的API获得401错误时,请删除令牌并尝试重新授权。 这里可以找到一个使用谷歌OAuth的完整示例扩展程序

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