Wiki

Clone wiki

openid-cookbook / Javascript Cookbook

Javascript Cookbook for OpenID Connect Public Client

Before Starting

In order for the client to make an OpenID Connect request, it needs to have the following basic OAuth 2.0 information about the OpenID Provider and the client:

[provider info]

  • end-user authorization endpoint – The authorization server’s HTTP endpoint capable of authenticating the end-user and obtaining authorization.
  • jwks_uri – The JSON Web Key URI from which one can fetch the public key of the provider. This is used in lieu of client_secret as it is a public client which does not have a client secret.

[client info]

  • client identifier – A unique identifier issued to the client (RP) to identify itself to the authorization server.
  • redirect URI – A URI which was registered as the URI to which the user agent should be redirected after user authentication and authorization.

[scope and other parameters]

  • scope – A list of scopes you are going to ask for. One of them has to be 'openid'
  • response_type – the response type you want

You can find these from developer documentation or through the optional discovery process. In this document, we will assume that you have gotten them out of band with the following values:

[provider info]

#!javascript

var providerInfo = {
                     issuer: 'https://op.example.com',
                     authorization_endpoint: 'http://op.example.com/auth.html',
                     jwks_uri: 'https://op.example.com/jwks'
                   };

[client info]

#!javascript

// set client_id and redirect_uri
var clientInfo = {
    client_id : 'MyClientId',
    redirect_uri : 'https://rp.example.com/callback.html'
};

[scope and other params]

#!javascript

var reqOptions = {
                        scope : 'openid profile',
                        response_type : 'token id_token',
                        max_age : 60,
                        claims : {id_token : ['email', 'phone_number']}
                 };

Let's start!

Make 'state' and 'nonce', and store it in the session

To prevent XSRF, you have to first create 'state', which is an OAuth 2.0 request parameter. It has to be a cryptographic safe random string so that it is extremely difficult for the attacker to guess.

#!javascript
    var state = null;
    var nonce = null;

    // Create state and nonce
    var crypto = window.crypto || window.msCrypto;
    if(crypto && crypto.getRandomValues) {
        var D = new Uint32Array(2);
        crypto.getRandomValues(D);
        state = D[0].toString(36);
        nonce = D[1].toString(36);
    }

    // Store the them in session storage
    sessionStorage['state'] = state;
    sessionStorage['nonce'] = nonce;

Feel too complicated? You do not have to do them manually if you use a library like openidconnect.js. If you use such a library, everything comes free.

Using the above parameters and state value, create the login link as:

#!javascript

    var login_link =
        providerinfo['authorization_endpoint']
            + '?response_type=' + reqOptions['response_type']
            + '&scope=' + reqOptions['scope']
            + '&nonce=' + nonce
            + '&client_id=' + client_info['client_id']
            + '&redirect_uri=' + client_info['redirect_uri']
            + '&state=' + state
            + '&max_age=' + reqOptions['max_age']
            + '&claims=' + "{\"claims\":{\"id_token\":{\"email\":null, \"phone_number\":null}}}";

Once you have created the login link, have the user click the link or otherwise take the user to the link.

Receive the callback and verify

After the user authenticates and authorizes, the response comes back to the Redirect URI. You need to evaluate it. We have been hand coding everything till here, but the evaluation of the response in a public client actually requires some asymmetric cryptography unlike in the case of a confidential client. You probably do not want to write it yourself. It is probably better to use a library. Here, I am using openidconnect.js.

#!javascript

var id_token = OIDC.getValidIdToken();
var code = OIDC.getCode();
var token = OIDC.getAccessToken();

These functions returns the valid ID Token, Code, and Access Token. If the validation fails, it will throw errors. Now that you have an ID Token, you can decode the payload and extract various claims from it as well.

NOTE: getValidIdToken is doing bunch of ID Token checking. Below is the checks that it is performing.

  1. The value of the state parameter is the same as the one stored in the session.
  2. The signature on the ID Token is valid and from the expected source.
  3. Check that the ID Token is issued before the time of checking.
  4. Check that the ID Token's expiry date is in the future.
  5. Check that the 'aud' claim value (audience) and its client_id matches.
  6. Check that the issuer of the ID Token is the one in the server configuration.
  7. Check that the nonce is equal to that was stored in the session.

Getting claims (attributes) out of ID Token

ID Token can have claims/attributes inside itself. In this example, we have asked email and phone number to be included in the ID Token, so if the server provides it, we can get them from it.

To do so, you need to parse the ID Token. 'openidconnect.js' provides a utility function for it.

#!javascript

payload = OIDC.getIdTokenPayload; 
email = payload.email;
phone = payload.phone;

Associate the local account with the iss/sub pair

With a valid ID Token, you know who is the user. In many cases, you need to associate it with the local account.

If you look up the iss/sub pair and you do not find it in an existing account, it is either a new user or an existing user whose account is not associated with this OpenID Identifier.

You have several options here.

  1. Ask the user to login with a local account as well so that you can associate the local account and the OpenID Identifier
  2. Use the email claim and use it to associate with the local account. You need to send a confirmation email to the user for the linkage though and should not fully log in the user until it is confirmed unless you are sure that the email in your local account is fresh and the server's verified email is correct.
  3. Create a new account using the OpenID identifier and link them later.

If you look up the iss/sub pair and find one in the local account, you are all set.

(Optional) Getting other claims from Userinfo Endpoint

Userinfo Endpoint is the server endpoint that serves the information related to the user. The response is somewhat similar to the payload of the ID Token but it does not include the authentication event data. Unlike ID Token, which is typically supposed to be small, the Userinfo response can be big and include many things including photo etc.

Accessing Userinfo Endpoint from a javascript is however slightly more complicated than accessing what is in ID Token: we need to use CORS.

[TODO] Fill in the details.

Updated