OpenID Connect

OpenID relies on OAuth2 bearer tokens, so basically authentication shall be done using this header:

Authorization: Bearer <access_token>

Note

If the token is not valid, this will result in a 401 Unauthorized error response.

Login and obtain access token

Once configured, the OpenID configuration details are provided in the capabilities object at the root URL:

HTTP/1.1 200 OK
Content-Length: 638
Content-Type: application/json

{
    "capabilities": {
        "openid": {
            "description": "OpenID connect support.",
            "providers": [
                {
                    "name": "google",
                    "auth_path": "/openid/google/login",
                    "client_id": "XXXXX.apps.googleusercontent.com",
                    "header_type": "Bearer",
                    "issuer": "https://accounts.google.com",
                    "userinfo_endpoint": "https://www.googleapis.com/oauth2/v3/userinfo"
                }
            ],
            "url": "http://kinto.readthedocs.io/en/stable/api/1.x/authentication.html"
        }
    }
}

In order to initiate the login and its browser redirections (aka. «dance»), just reach the URL specified in the auth_path for the configured provider name.

GET /openid/{name}/login?callback={URI}&scope={scope}
  • callback which is the URI the browser will be redirected to after the login (eg. http://dashboard.myapp.com/#tokens=). It will be suffixed with the JSON response from the Identity Provider, which will either be the access and ID tokens or an error.
  • scope which should at least be openid (but usually openid email) (see your Identity Provider documentation)
  • prompt (optional) if set has to be the word none. Generally used for Silent Authentication.

Note

Because multiple OpenID providers can be enabled on the server, the auth_path URI contains the provider name with which the login process is initiated (eg. client initiates login by redirecting to /openid/auth0/login?... or /openid/google/login?... etc.)

Note

The effect of adding &prompt=none tells the Identity Provider to not present a 200 OK response. Only redirects.

JavaScript example

Let’s go through a simple OpenID login example using the JavaScript kinto client.

When the user clicks a login button, it initiates the login process by redirecting the browser to the Identity Provider, which itself redirects it to the application page once successful.

const KINTO_URL = 'http://localhost:8888/v1';

const SCOPES = 'openid email';

// Redirect to the same page using the location hash.
const CALLBACK_URL = window.location.href + '#tokens=';

const kintoClient = new KintoClient(KINTO_URL);

document.addEventListener('DOMContentLoaded', async () => {
  // Initiate login on some button click
  loginBtn.addEventListener('click', login);

  // Check if the location contains the tokens (after being redirected)
  const authResult = parseToken();
  if (authResult) {
    const {access_token, token_type} = authResult;
    if (access_token) {
      // Set access token for requests to Kinto.
      kintoClient.setHeaders({
        'Authorization': `${token_type} ${access_token}`,
      });
      // Show if Kinto authenticates me:
      const {user} = await kintoClient.fetchServerInfo();
      alert("You are " + (user ? user.id : "unknown"));
    }
    else {
      console.error('Authentication error', authResult);
    }

  }
});

The login() function is straightforward:

function login() {
  const {capabilities: {openid: {providers}}} = await kintoClient.fetchServerInfo();
  // Use the first configured provider
  const {auth_path} = providers[0];
  // Redirect the browser to the authentication page.
  const callback = encodeURIComponent(CALLBACK_URL);
  window.location = `${KINTO_URL}${auth_path}?callback=${callback}&scope=${SCOPES}`;
}

The parseToken() function scans the location hash to read the Identity Provider response:

function parseToken() {
  const hash = decodeURIComponent(window.location.hash);
  const tokensExtract = /tokens=([.\s\S]*)/.exec(hash);
  if (!tokensExtract) {
    // No token in URL bar.
    return null;
  }
  const tokens = tokensExtract[1];
  const parsed = JSON.parse(tokens);
  return parsed;
}

Check out the full demo source code.

Example of login redirections

Let’s assume the JavaScript app is accessible on http://localhost:3000 and the Kinto server running on http://localhost:8888.

When the user clicks the login button, the browser will follow a sequence of redirections similar to this one:

  1. User clicks on the auth0 login button
  2. JavaScript redirects to http://localhost:8888/v1/openid/auth0/login?scope=openidemail&callback=http://localhost:3000/#provider=auth0&tokens=
  3. Kinto generates and stores a state string
  4. Kinto redirects to Auth0 that will show the login form https://minimal-demo-iam.auth0.com/authorize?client_id=BXqGVgl2meRsdVK0dEZPTk516JUhje2M&response_type=code&scope=openid+email&redirect_uri=http://localhost:8888/v1/openid/auth0/token?&state=3a309f5baba
  5. User enters credentials and authenticates
  6. Auth0 redirects to Kinto with the state and a code http://localhost:8888/v1/openid/auth0/token?code=lWpsu9VoHLJEVyy1&state=3a309f5baba
  7. Kinto checks that the state matches
  8. Kinto trades the code against the ID and Access tokens
  9. Kinto redirects back to the Single Page App appending the JSON encoded ID and Access tokens to the callback URL provided at step 2 http://localhost:3000/#provider=auth0&tokens={“access_token”:”tY6um989…jfcer”,”id_token”:”eyJ0eXAiOiJ…KV1QiL.ojkhgwRVH…UG8JGRENNF.Es8…DK10”,”expires_in”:86400,”token_type”:”Bearer”}
  10. JavaScript code parses the location hash and reads the ID and Access tokens

The JavaScript app can now use the Access token to make authenticated calls to the Kinto server, and read the user info from the ID token fields. See demo.