Skip to content

curityio/pkce-javascript-example

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 

Repository files navigation

A Simple JavaScript PKCE Example

Quality Availability

Introduction

The OAuth Code Flow is one of the more typical and flexible token flows, and, with that, one of the most popular. The details of this flow are not covered by this article, but can be found in the code flow overview article on the Curity Web site.

Proof Key for Code Exchange (PKCE) is a technique described in RFC7636, and is used to mitigate the risk of the authorization code being hijacked. More details on how to configure the Curity Identity Server to enable PKCE can be found in the configuring PKCE tutorial, and further details on PKCE can also be found on the same site.

The rest of this writeup explains how these technologies can be used in the JavaScript programming language. It is intentionally simple, so that the concepts are not obscured by superfluous details.

Configuration

Client

The client -- the HTML page -- needs to be configured with the client ID. In this example, the ID is public-test-client. If certain scopes are desired, these should be configured as well.

const clientId = "public-test-client";

Creating the Verifier

This function generates a random string (the verifier) that is later signed before it is sent to the authorization server, to Curity.

function generateRandomString(length) {
  var text = "";
  var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

  for (var i = 0; i < length; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }

  return text;
}

Hashing the Verifier

The Web crypto API is used to hash the verifier using SHA-256. This transformed version is called the code challenge.

async function generateCodeChallenge(codeVerifier) {
  var digest = await crypto.subtle.digest("SHA-256",
    new TextEncoder().encode(codeVerifier));

  return btoa(String.fromCharCode(...new Uint8Array(digest)))
    .replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
}

Note that Javascript crypto services require that the index.html is served in a secure context — either from (*.)localhost or via HTTPS. To enable secure context do one of the following:

  • add an /etc/hosts entry like 127.0.0.1 public-test-client.localhost and load the site from there, or
  • enable SSL using something like letsencrypt, or
  • refer to this stackoverflow article for more alternatives.

If Javascript crypto is not available the script will fall back to using a plain-text code challenge.

Storing the Verifier

Store the verification key between requests (using session storage).

window.sessionStorage.setItem("code_verifier", codeVerifier);

Sending the Code Challenge in the Authorization Request

The code challenge (the transformed, temporary verification secret) is passed to the authorization server as part of the authorization request. The method (S256, in our case) used to transform the secret is also passed with the request.

var redirectUri = window.location.href.split('?')[0];
var args = new URLSearchParams({
  response_type: "code",
  client_id: clientId,
  code_challenge_method: "S256",
  code_challenge: codeChallenge,
  redirect_uri: redirectUri
});
window.location = authorizeEndpoint + "/?" + args;

Call the Token Endpoint with the Code and Verifier

The authorization code is passed in the POST request to the token endpoint along with the secret verifier key (retrieved from the session storage).

xhr.responseType = 'json';
xhr.open("POST", tokenEndpoint, true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send(new URLSearchParams({
  client_id: clientId,
  code_verifier: window.sessionStorage.getItem("code_verifier"),
  grant_type: "authorization_code",
  redirect_uri: location.href.replace(location.search, ''),
  code: code
}));

OAuth Server

The OAuth server needs to be configured with a client that matches the one configured above. Also, the redirect should be set. When using npx (described below), this will be http://localhost:8080 by default if no port is provided. Additionally, scopes may be configured.

This can be created in the Curity Identity Server by merging this XML with the current configuration:

<config xmlns="http://tail-f.com/ns/config/1.0">
  <profiles xmlns="https://curity.se/ns/conf/base">
  <profile>
    <id>my-good-oauth-profile</id> <!-- Replace with the ID of your OAuth profile -->
    <type xmlns:as="https://curity.se/ns/conf/profile/oauth">as:oauth-service</type>
      <settings>
      <authorization-server xmlns="https://curity.se/ns/conf/profile/oauth">
      <client-store>
      <config-backed>
      <client>
        <id>public-test-client</id>
        <no-authentication>true</no-authentication>
        <redirect-uris>http://localhost:8080/</redirect-uris> <!-- Update with your URL -->
        <capabilities>
          <code/>
        </capabilities>      
        <validate-port-on-loopback-interfaces>false</validate-port-on-loopback-interfaces>
      </client>
      </config-backed>
      </client-store>
      </authorization-server>
      </settings>
  </profile>
  </profiles>
</config>

Serving the Sample HTML File

The HTML needs to be served somehow from a Web server. Because the client is just a static HTML page, this can be done with a trivial server configuration. Below are a couple of ways to easily serve the static HTML page:

$ npx http-server -p <port>
$ php -S <host>:<port>
$ python -m SimpleHTTPServer <port>

These will not use TLS, but are fast and easy ways to serve the HTML file without setting up any infrastructure.

License

The code and samples in this repository are licensed under the Apache 2 license.

Questions

For questions and comments, contact Curity AB:

info@curity.io https://curity.io

Copyright (C) 2020 Curity AB.

Releases

No releases published

Packages

No packages published

Languages