« Back to home

oidc-token-manager library with Google Identity Platform - Part 1

Daj się poznać

As I stated in this post, I am going to write a series of posts about auth stuff. This post will be the second in this series and I am describing my experiences trying to use the library oidc-token-manager to get tokens from the Google Identity Platform.

As I mentioned before, I want to have a single page application which gets tokens from Identity Provider and sends them to REST API to get access to resources.

To validate this solution, I was searching for a pure javascript library which allows me to get a token from OpenId Connect Identity Provider, refreshes this token (yes, even in implicit flow), gives me access to some information from it, stores this token and destroys it on my request.

I was looking for a library which fulfills my needs and I found one. It is called oidc-token-manager. This library has everything what I want: Redirects for token
Processes token
Removes token
Renews token

But this library is dedicated for IdentityServer which is a .NET implementation of protocols like OpenID Connect and OAuth2. I wanted to use it with Google not with IdentityServer, but Google Identity Provider is also compatible with OpenID Connect and OAuth2. There was hope and below I have written about how I verified this hope.

I didn’t use my Notifier project to test this library. I did this with a small sample application, so let’s jump right into it and see how it looked.

The library oidc-token-manager has a nice sample project in its repository. I used it as a starting point.

I created a new folder (mkdir NotifierDemo) and inside it I copied everything what was in this folder. I removed all the stuff related to Visual Studio. My file structure looked like this in WebStorm:

File tree

I needed a server to host my application and I decided to use a simple Node.js server called http-server but first I had to create a package.json file. I did this using npm init, after answering every question I had the file package.json inside my project.

Then I installed http-server and saved it as a development dependency using this command: npm install http-server --save-dev. Then I added script dev to package.json so the scripts section looked like this:

"scripts": {
 "dev": "http-server -p 5000"
},

The option -p is a port on which the server will be running. I had to set this port to 5000 because I specified it for my client in the Google Developer Console.

Now I ready to start connecting my application with Google Identity Provider.

I started in the file index.html and his file has a config variable which contains the whole configuration for oidc-token-manager. I had the setting needed for this configuration in the file which I downloaded at the end of the previous post. This file looks like this:

{"web":{"client_id":"342665198077-tp56a5pab4ei5lri37nkba69b6sqghou.apps.googleusercontent.com","project_id":"notifierdemo-1268","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"jDvSf78IkYDGEw9FI_gnXEnZ","redirect_uris":["http://localhost:5000/callback.html"],"javascript_origins":["http://localhost:5000"]}}

I took client_id which was in the property with the same name. I also changed scope in config and only left email in scope and I set authority property. At this stage, my config in index.html looked like this:

var config = {
    authority: "https://accounts.google.com",
    client_id: "342665198077-tp56a5pab4ei5lri37nkba69b6sqghou.apps.googleusercontent.com",
    redirect_uri: window.location.protocol + "//" + window.location.host + "/callback.html",
    post_logout_redirect_uri: window.location.protocol + "//" + window.location.host + "/index.html",
    response_type: "id_token token",
    scope: "email",
    silent_redirect_uri: window.location.protocol + "//" + window.location.host + "/frame.html",
    popup_redirect_uri: window.location.protocol + "//" + window.location.host + "/popup.html",
    silent_renew: true
};

I also set authority and client_id in the config variable in callback.html so it looks like this;

var config = {
    authority: "https://accounts.google.com",
    client_id: "342665198077-tp56a5pab4ei5lri37nkba69b6sqghou.apps.googleusercontent.com"
};

It seemed to me that I was ready, so I ran my application with the command npm run dev. I navigated in the browser to http://localhost:5000 and I saw this:

Application Window

I clicked on the Get Token button. And nothing happened. And then the investigation began. I opened a browser console and saw:

Not allowed access

It’s a well known issue. You can read a lot about it on the Internet. To get rid of it quickly I used this chrome extension. This extension gave me a button which allows me to enable cross-origin resource sharing. This was a very ugly solution but for my testing purposes I accepted it.

Enable cross-origin resource sharing

After enabling cross-origin resource sharing. I navigated to http://localhost:5000 again and I clicked on the Get Token button once more. And this time I got following screen:

NotifierDemo would like to

After clicking Allow I got this screen:

RSA keys empty

After spending some time debugging, I finally got my answer. The library oidc-token-manager fetches metadata from a .well-known url. It is specified in the OpenID Connect Discovery 1.0 and in the case of Google, it looks like this https://accounts.google.com/.well-known/openid-configuration. The response from Google .well-known url provides among others this property:

"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",

From this url: https://www.googleapis.com/oauth2/v3/certs, oidc-token-manager fetches the necessary certificates to validate the tokens. This library assumes that the information about certs is provided as an object something like this:

{
  "keys": [{
    "kty": "RSA",
    "alg": "RS256",
    "use": "sig",
    "kid": "8087d258ac19c0fcf1dab7a908c221cdd81d5512",
“x5c”:["-----BEGIN CERTIFICATE-----\nMIIDJjCCAgwIBA… [removed for brevity] ...ZTRsIQWyJ4hUP\n-----END CERTIFICATE-----\n",
  "c8f37d70371587d2aaae3bbff624cc865ef10575": "-----BEGIN CERTIFICATE-----\nMIIDJjCCAg… [removed for brevity]  ...CAVCztq15Z\n-----END CERTIFICATE-----\n"
]
  }, {
    "kty": "RSA",
    "alg": "RS256",
    "use": "sig",
    "kid": "428489e3a6753680152ffcf1a8f7d0379f28ce9e",
    “x5c”:["-----BEGIN CERTIFICATE-----\nMIIDJjCCAgwIBA… [removed for brevity] ...ZTRsIQWyJ4hUP\n-----END CERTIFICATE-----\n",
  "c8f37d70371587d2aaae3bbff624cc865ef10575": "-----BEGIN CERTIFICATE-----\nMIIDJjCCAg… [removed for brevity]  ...CAVCztq15Z\n-----END CERTIFICATE-----\n"
] }]
}

But Google provides the following structure of information about certificates:

{
  "keys": [{
    "kty": "RSA",
    "alg": "RS256",
    "use": "sig",
    "kid": "8087d258ac19c0fcf1dab7a908c221cdd81d5512",
    "n": "pmYJZAbSY2L …[removed for brevity]... 9dQNxhFaixl4BgqjaP9onw",
    "e": "AQAB"
  }, {
    "kty": "RSA",
    "alg": "RS256",
    "use": "sig",
    "kid": "428489e3a6753680152ffcf1a8f7d0379f28ce9e",
    "n": "pqLUr6XU-Ut4z_JS- …[removed for brevity]... H1QWo8bs2SXo3w",
    "e": "AQAB"
  }]
}

The missing part is property x5c. In this property, there is a public key needed to validate the signature of the tokens. Google provides a modulus(n) and an exponent(e) RSA public key but library oidc-token-manager expects a PEM key which looks something like this:

"-----BEGIN CERTIFICATE-----\nMIIDJjCCAgwIBA… [removed for brevity] ...ZTRsIQWyJ4hUP\n-----END CERTIFICATE-----\n"

I also found that Google provides the following information in the header part of the JSON Web Token.

{"alg":"RS256","kid":"428489e3a6753680152ffcf1a8f7d0379f28ce9e"}

The property kid indicates which public key from https://www.googleapis.com/oauth2/v3/certs, should be used to validate the signature. As it is an array of keys, you must choose one. The library oidc-token-manager always takes the first one.

I managed to get access token from Google Identity Provider using library oidc-token-manager but I couldn’t validate its signature because of the format of the certs returned from Google but there must be a way to handle this kind of situation. However, I’ll write about this in the next post.

Related posts:

Comments

comments powered by Disqus