Sign Up
Hero

Authenticable Observables: Build a Reactive App with JWT Auth

Learn how to make a reactive application and add authentication to it with RxJS and the Fetch API


TL;DR: Reactive programming is powerful and is becoming increasingly popular with libraries such as RxJS. We can make a stream out of just about anything, including API communication and authentication. Check out the repo for this tutorial to see the code.


Reactive programming has been gaining a lot of popularity in recent years. In the Reactive paradigm, data is handled as streams that can be composed of anything from events to simple variables and everything in between. Much like a stream of flowing water is a continuous phenomenon, a data stream in Reactive programming is also continuous and can be observed. When we observe streams over time, we are most interested in the events emitted from them, which happen asynchronously. In this way, the streams become "observable", and we can subscribe to them to react in whichever ways we specify when the events are emitted.

In this tutorial, we'll see how we can implement Reactive programming by creating a simple app that fetches and displays data. We'll also see how we can handle authentication in our app and have what we'll call "authenticable observables". Our goal throughout this tutorial is to see how we can shift our thinking about app architecture. We'll move away from the simple Promise-based asynchronous models we're used to and see how we can benefit from working with Observables.

"In the Reactive paradigm, data is handled as streams that can be composed of anything from variables to events"

Tweet This

Building Our Reactive App

We'll make use of Auth0's trusty NodeJS backend that serves us both public and private Chuck Norris quotes. The API uses JWT authentication, which is perfect for our case, as we'll want to see how we can use observables to handle JWTs. We'll also be using The Reactive Extensions for JavaScript, otherwise known as RxJS, which is a set of libraries that provide a wealth of tooling for working with observables. To actually retrieve data, we'll use the Fetch API.

In the interest of brevity, we won't deal with error conditions. However, the Fetch API and RxJS both have methods for handling errors, as would be expected. Also, since we want to focus mainly on how to handle observables for our sample application, we'll keep things simple and won't worry too much about any kind of routing or other ways we could polish the app. Instead, we'll provide some simple controls for the user to log in and retrieve public and private quotes.

Setting up the HTML

Let's first set up the HTML. We'll include RxJS from the CDN and use Bootstrap for styling as well:

  <!-- index.html -->

  ...

  <nav class="navbar navbar-default">
    <div class="container">
      <div class="row">
        <div class="col-sm-4">
          <input id="username" class="form-control" type="text" placeholder="Enter your username">          
        </div>

        <div class="col-sm-4">
          <input id="password" class="form-control" type="password" placeholder="Enter your password">          
        </div>

        <div class="col-sm-4">
          <button id="login-button" class="btn btn-primary">Login</button>          
        </div>
      </div>
    </div>
  </nav>

  <div class="container">    

    <button id="quote-button" class="btn btn-primary">
      Get a Quote!
    </button>
    <button id="secret-quote-button" class="btn btn-primary">
      Get a Secret Quote!
    </button>

    <h1></h1>

  </div>

  </body>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.3.22/rx.all.js"></script>
  <script src="app.js"></script>

  ...

Creating the Click Streams

RxJS gives us a fromEvent method that allows us to create streams from any kind of event. In our case, we'll need to observe click events on our three buttons. We first need to select the buttons using querySelector and then pass that selection into the fromEvent method as the first argument, along with 'click' as the second argument.

// app.js

...

// Selectors for our buttons
var loginButton = document.querySelector('#login-button');  
var quoteButton = document.querySelector('#quote-button');
var secretQuoteButton = document.querySelector("#secret-quote-button");

// We can create streams from click events on our buttons using
// Rx's 'fromEvent' method
var quoteClickStream = Rx.Observable.fromEvent(quoteButton, 'click');
var secretQuoteClickStream = Rx.Observable.fromEvent(secretQuoteButton, 'click');
var loginClickStream = Rx.Observable.fromEvent(loginButton, 'click');

...

With these streams set up, we could at this point subscribe to the events and respond in some simple way when they are observed. However, for our purposes, we'll need to create additional streams out of them first.

Creating New Streams

When working with streams, we will often need to combine or create new streams out of existing ones. RxJS gives us many useful ways of doing this, and one of them is map. This method projects a value we pass into it into a new stream that is dependent on an existing stream.

Let's make a new stream called quoteStream that takes care of getting the publicly accessible quotes. We'll need to create a new stream that maps a value (in this case an object with a route key) to the quoteClickStream.

// app.js

...

// Here we want to map the endpoint for where we'll get our public quotes
var quoteStream = quoteClickStream
  .map(function() {
    return {
      route: 'http://localhost:3001/api/random-quote'
    }
  });

...

The map operation transforms the quoteClickStream into a new stream.

Getting Public Quotes

We'll need to create another stream after this that is responsible for calling a function that fetches data from the API. We'll call this one quoteResponseStream because it is responsible for getting responses.

// app.js

...

// We need a response stream that handles the fetch operation
var quoteResponseStream = quoteStream
  // flatMap flattens the new stream such that we can get access
  // to JSON data returned from the fetch operation
  .flatMap(function(request) {
    return fetchQuote(request);
  });

...

The quoteResponseStream relies on the quoteStream, which, as we saw above, maps an object with a url key that can be passed to the fetchQuote method to make the request. Since the values emitted by the map method are themselves streams, we need a way to get access the actual data. In this new stream we are using flatMap which effectively flattens the emitted values and gives us access to the JSON returned by the fetch operation.

We are returning the fetchQuote function with the request URL but we haven't yet set it up.

// app.js

...

function fetchQuote(request) {
  return fetch(request.route).then(function(data) {
    return data.text().then(function(text) {
      return text;
    });
  });
}

...

The fetchQuote function makes a GET request to the route we pass in and returns the data as text.

With this set up, we're now ready to subscribe to the quoteResponseStream and observe events. We can do whatever we want with the returned responses, and in this case we'll simply print them on the page.

// app.js

...

// We subscribe to the quoteResponseStream, listen
// for quotes that get returned, and put them on the page
quoteResponseStream.subscribe(function(text) {
  document.querySelector('.container h1').innerHTML = text;
});

...

If we now click the "Get a Quote!" button, we get a public quote back.

Setting up the Auth Streams

To authenticate the user and receive a JWT, we could follow the same approach we saw above, in which we would map a route and then call a function to fetch the JWT. However, we can also create a custom RxJS operator to abstract those details away. Let's take the latter approach and create an authenticate operator that will retrieve a JWT for the user and put it in local storage.

First, we'll need to create a new stream called loginStream out of the existing loginClickStream.

// app.js

...

// .map will create a new stream that is dependent on the first
// and will project a value that we pass it. The login stream needs to
// map a string that contains values from the username and password input boxes
var loginStream = loginClickStream
  .map(function() {
    var loginPath = 'http://localhost:3001/sessions/create';
    var username = document.querySelector('#username').value;
    var password = document.querySelector('#password').value;
    var method = 'POST';
    var headers = { 'Content-Type':'application/x-www-form-urlencoded' };
    return {
      loginPath: loginPath,
      username: username,
      password: password,
      method: method,
      headers: headers
    }
  })
  .authenticate(function(config) {
    return config;
  });

...

Here we're setting up some configuration for the authenticate operator, including the path to use for authentication, the username, and the password. After mapping that configuration to the loginClickStream, we use an anonymous function to return it for the authenticate operator to use.

We can define a new operator on Rx.Observable.prototype.

// app.js
...

Rx.Observable.prototype.authenticate = function(config) {

  // We need a function to handle the fetch request for the JWT
  function getJwt(config) {
    var body;

    // Check the content type header and format the request data accordingly
    if(config.headers['Content-Type'] === 'application/x-www-form-urlencoded') {
      body = 'username=' + config.username + '&password=' + config.password;
    }

    // We need some config to specify that this is to be a POST request
    // We set the content type and put the passed-in credentials as the body
    var init = {
      method: config.method,
      headers: config.headers,
      body: body
    }

    return fetch(config.loginPath, init).then(function(data) {
      return data.json().then(function(jwt) {
        localStorage.setItem('id_token', jwt.id_token);
        localStorage.setItem('access_token', jwt.access_token);
      });
    });        
  }

  return this.flatMap(function(credentials) {
    return getJwt(credentials);
  });    
}

...

Here we are accepting a configuration object and setting up the request type, headers, and body that we want to send to the API. Once the request resolves, we set the returned tokens into local storage.

We can now log in with our credentials. For this simple example, we don't have a way to register users (although the NodeJS example back end allows it), so we'll just use the default username of "gonto" and a password of "gonto".

Retrieving Secret Quotes

Now that we've got our authentication in place and we have an id_token and access_token saved in local storage, let's set up the streams that handle secret quotes. We'll need a way to check whether there is a JWT access_token in local storage before we send the request. Let's once again abstract this task away into a custom operator called authenticated.

// app.js

...

Rx.Observable.prototype.authenticated = function(route) {

  var jwt = localStorage.getItem('id_token');

  // Return the JWT if it exists
  if(jwt != undefined && jwt != null) {
    return this.map(function() {
      return {
        route: route,
        jwt: jwt
      }
    });
  }
  // If there is no JWT, throw an error
  else return Rx.Observable.throw(new Error('No JWT in Local Storage!'));
}
...

The authenticated operator will check for a JWT access_token in local storage, and if there is one, it will return an observable by mapping out an object with the route for our request and the JWT.

Next, we can make use of this operator in our secretQuoteStream by passing in the route we want to make the request to. In this case, it's the route for secret quotes.

// app.js

...

// We need a separate stream for the secret quotes which
// checks authentication
var secretQuoteStream = secretQuoteClickStream
  .authenticated('http://localhost:3001/api/protected/random-quote');

...

The Secret Quote Response

We could set up a separate response stream for our secret quotes that simply mimics the quoteResponseStream, but instead of repeating ourselves, let's use RxJS's merge method to combine the quoteStream and the secretQuoteStream into one single quoteResponseStream. This gives us the benefit of only needing to subscribe to a single stream that will observe events for public and private quotes.

// app.js

...

// We need a response stream that handles the fetch operation
var quoteResponseStream = quoteStream
  .flatMap(function(request) {
    return fetchQuote(request);
  })
  // Instead of setting up a separate stream for the secret quotes, we
  // can simply merge the secretQuoteStream in and apply the same flatMap
  .merge(secretQuoteStream
    .flatMap(function(request) {
      return fetchQuote(request);
    })
  );

...

We just need to modify the fetchQuote function so that it properly handles the JWT that might be present. If the JWT is there, it will be included as an Authorization header and sent to the API.

// app.js

...

// One fetch function will handle getting both the public and private quotes
function fetchQuote(request) {
  // We just need to check whether a JWT was sent in as part of the
  // config object. If it was, we'll include it as an Authorization header
  if(request.jwt) {
    var config = {
      headers: {
        "Authorization": "Bearer " + request.jwt
      }
    }
  }

  return fetch(request.route, config).then(function(data) {
    return data.text().then(function(text) {
      return text;
    });
  });
}

...

Since we merged the secretQuoteStream into the quoteResponseStream, we don't need to modify the subscription at all. Testing this out, we see that we now get secret quotes.

Aside: Using Authenticable Observables with Auth0

Auth0 issues JSON Web Tokens on every login for your users. This means that you can have a solid identity infrastructure, including Single Sign On, User Management, support for Social (Facebook, Github, Twitter, etc.), Enterprise (Active Directory, LDAP, SAML, etc.) and your own database of users with just a few lines of code.

The custom authenticate operator we created above can be extended for use with Auth0's authentication API very easily. Let's see this in action with an example using Auth0's test API.

First, let's add a check to the getJwt method within authenticate to see if Auth0 should be the provider:

// app.js

...

if(config.headers['Content-Type'] === 'application/json') {
  if(config.provider === 'Auth0') {
    body = JSON.stringify({
        client_id: config.client_id,
        username: config.username,
        password: config.password,
        connection: config.connection,
        grant_type: config.grant_type,
        scope: config.scope
      });          
  }
}

...

Next, let's adjust the loginStream to map info that is specific to Auth0's test API.

// app.js

...

var loginStream = loginClickStream
  .map(function() {
    var provider = 'Auth0';
    var loginPath = 'https://samples.auth0.com/oauth/ro'
    var client_id = 'BUIJSW9x60sIHBw8Kd9EmCbj8eDIFxDC';
    var username = document.querySelector('#username').value;
    var password = document.querySelector('#password').value;
    var connection = 'Username-Password-Authentication';
    var grant_type = 'password';
    var scope = 'openid';
    var headers = { 'Content-Type':'application/json' };
    var method = 'POST';
    return {
      loginPath: loginPath,
      client_id: client_id,
      connection: connection,
      grant_type: grant_type,
      scope: scope,
      username: username,
      password: password,
      method: method,
      headers: headers
    }
  })
  .authenticate(function(config) {
    return config;
  });

...

If we enter a username of "hello@bye.com" and password of "hello", we see that we get a JWT back.

Important API Security Note: If you want to use Auth0 authentication to authorize API requests, note that you'll need to use a different flow depending on your use case. Auth0 idToken should only be used on the client-side. Access tokens should be used to authorize APIs. You can read more about making API calls with Auth0 here.

If you'd like to explore Auth0's API further, head over to the docs. For help while working with JWT's you can also check out Auth0's JWT debugger.

Wrapping Up

You might be wondering why we'd bother architecting our applications using observables. It seems like we just used a lot more code than was really necessary and that we could have been more succinct using more-familiar methods. While that might be true, observables offer so much more power and flexibility than Promises that making the switch can be worthwhile. For example, if we wanted to provide better support for network latency or other constraints where a request and response might not go through as expected, we could tap into RxJS's retry method, which would allow us to set the number of times we wanted to retry a request before having it error out.

With this tutorial we've really just skimmed the surface. The possibilities that we have with Reactive programming and supporting libraries such as RxJS are huge. If you like the idea of turning nearly all the parts of your application into data streams, it would certainly be beneficial to explore Reactive programming further.

Big thanks to André Staltz for his article on Reactive programming, which informed many of these examples.