Is it possible to get an Id token with Chrome App Indentity Api?
Asked Answered
A

3

20

I can't get a user's id_token (https://developers.google.com/accounts/docs/CrossClientAuth) from the Chrome identity api (https://developer.chrome.com/apps/identity).

I can get an access_token using the chrome identity sample when the oauth section in the manifest is:

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

But when I try to get the id_token the same way I get it on my android client a get the error:

"OAuth2 request failed: Service responded with error: 'invalid scope: {0}'"}

The manifest section is now:

"oauth2": {
  "client_id": "<chrome-app-client-id>.apps.googleusercontent.com",
  "scopes": ["audience:server:client_id:<app-engine-client-id>.apps.googleusercontent.com"]
}

On Android I get the id_token by passing the same scope string to android.gms.auth.GoogleAuthUtil.getToken(), but I can't get it to work with the chrome identity api.

Is it possible to get an id_token with Chrome App Indentity Api? If not, how can I get an id_token for my Chrome app?

Thanks for your help!

Attract answered 8/10, 2014 at 11:47 Comment(3)
The terminology with OAuth is crazy. In your post, you refer to "user id", "client_id", "id token", and "id". Can you be precise about what is it exactly that you are trying to get? (I know a few things about a "client_id", "id" is too generic for me to understand, and I'm not sure what a "user id" is.)Ergener
Agree, there are a few terms you need to keep separate. Edited the post for clarity. The client_id is mentioned on the first link in the post, it's needed to specify the audience in the id_token. I think it's clear that I want an id_token that can be verified when appended to googleapis.com/oauth2/v1/tokeninfo?id_token=Attract
Have you tried adding openid to the scopes? This is usually a required hint to get the server into OpenID Connect mode and issue an ID Token.Ursulina
J
23

I've came to the same problem yesterday and since I've found a solution, I might as well share it, as it wasn't that obvious. As far as i know Google does not provide a direct and documented way to do this, but you can use the chrome.identity.launchWebAuthFlow() function.

First you should create an Web application credentials in google console and add the following url as a valid Authorized redirect URI: https://<EXTENSION_OR_APP_ID>.chromiumapp.org. The URI does not have to exist, chrome will just catch the redirect to this URL and call your callback function later.

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 (for OpenID Connect) documentation can be found here: https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters

PS: If you don't need the oauth2 section in your manifest. You can safely omit it, and provide the identifiers and scopes in code only.

EDIT: For those interested, you don't need the identity API. You can even access the token using a little trick with tabs API. The code is a little longer, but you have better error messages and control. Keep in mind that in the following example, you need to create Chrome App credentials.

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});
});
Jeanicejeanie answered 13/9, 2015 at 9:4 Comment(8)
Hi Piotr. I've tried your solution but I'm getting Authorization page could not be loaded error. I'm doing exactly what you are doing, with the same scopes in the manifest.Mascon
Hi @Aegidius, did you figure this out? I'm having the same issue. Thanks.Trochlear
@Aegidius, same here, not sure if this method is still valid?Tupper
Two changes need to be made to this. 1) New endpoint base is https://accounts.google.com/o/oauth2/v2/auth 2) nonce is a required URL parameter for the authentication URL, so add something like &nonce=<your nonce value>Colloquialism
id_token is recommended when communicating user information from an extension to a backend server (outlined here: developers.google.com/identity/sign-in/web/backend-auth). I cannot for the life of me get this working, though, since I get the message "Invalid Redirect: domain must be added to the authorized domains list before submitting." when attempting to create an OAuth Web Client from the credentials console. Adding the extension's URL to authorized domains does not work because the domains "Must be a top private domain." Seems like a Catch-22... not sure what to do.Homicidal
I did actually get the tabs hack working (thanks Piotr), but that UX will not work for my purposes.Homicidal
The background page opening a new tab worked, thank you!! Seems like my page_action could not redirect itself to the login pageFecundate
this solves my problem very well!! echoing @dadykhoff, the url has to be updated and we need to add nonceFarmyard
E
0

First, I assume that in your manifest.json snippet you don't mean that your client_id is literally "<chrome-app-client-id>.apps.googleusercontent.com. It should be something like 9414861317621.apps.googleusercontent.com -- something you got from the Developer Console, or whatever Google site you used to register the app.

Assuming the above is OK, and you have the client_id right, and the scope right, you get what's called an "OAuth2 access token" with a call to chrome.identity.getAuthToken. As you don't show us any JavaScript code, I can't tell if this is what you're doing. The access token you get you need to save for subsequent use when you call an API function. For example:

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

Then, when you make an API call, you supply that access token, like this:

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 is my own function:

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;
})();

The other undefined functions also are what you'd expect. The third argument to Ajax.ajaxSend is a header to be sent along. (Sorry, I don't have time to develop standalone code just for this answer.)

I hope the above is useful.

Ergener answered 9/10, 2014 at 18:26 Comment(3)
Thanks for trying to help, but this is not an answer to my question. In my question on line 3 I say that I know how to get an access_token (I included that to avoid answers explaining how to get one). What I'm trying to get is an id_token (a token that can be verified when appended to this url: "googleapis.com/oauth2/v1/tokeninfo?id_token=" )Attract
Did you ever figure this out? I'm also trying to do the same. Thanks.Trochlear
still struggling with this :/Ailanthus
C
0

I guess it depends on why one would want token id, but in my case access_token was enough to authorize user - by pulling user info from https://www.googleapis.com/oauth2/v2/userinfo?alt=json ( with Authorization header = access_token).

Conformal answered 2/6, 2018 at 17:14 Comment(1)
id_token can be passed to a server saying "yes, I am this user -- google says I am, and that I intend to use this approval for your app (the project client_id), and you can verify the claim using google's public .well-known certificate"Fecundate

© 2022 - 2024 — McMap. All rights reserved.