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

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


Instead of authenticating the user using the chrome.identity.getAuthToken , just implement the OAuth part yourself.

You can use libraries to help you, but the last time I tried the most helpful library (the Google API Client) will not work on a Chrome extension.

Check out the Google OpenID Connect documentation for more info. In the end all you have to do is redirect the user to the OAuth URL, use your extension to get Google's answer (the authorization code) and then convert the authorization code to an access token (it's a simple POST call).

Since for a Chrome extension you cannot redirect to a web server, you can use the installed app redirect URI : urn:ietf:wg:oauth:2.0:oob. With this Google will display a page containing the authorization code.

Just use your extension to inject some javascript code in this page to get the authorization code, close the HTML page, perform the POST call to obtain the user's email.


Based on David's answer, I found out that chrome.identity (as well as generic browser.identity) API now provides a chrome.identity.launchWebAuthFlow method which can be used to launch an OAuth workflow. Following is a sample class showing how to use it:

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

DISCLAIMER: above class is based on open-source sample code from Mozilla Developer Network.

Usage:

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

Of course, you need to handle the expiration of tokens yourself i.e. when you get 401 from Google's API, remove token and try to authorize again.

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