npm package: @morphatic/feathers-auth0-authorize-hook
Description: A "before" hook for FeathersJS to authorize requests accompanied by an Auth0-issued JWT.
Author: Morgan Benton
Latest version: 1.0.3

# FeathersJS Auth0 Authorization Hook

[![Build Status](https://travis-ci.org/morphatic/feathers-auth0-authorize-hook.svg?branch=master)](https://travis-ci.org/morphatic/feathers-auth0-authorize-hook)
[![Coverage Status](https://coveralls.io/repos/github/morphatic/feathers-auth0-authorize-hook/badge.svg?branch=master)](https://coveralls.io/github/morphatic/feathers-auth0-authorize-hook?branch=master)
[![npm version](https://badge.fury.io/js/%40morphatic%2Ffeathers-auth0-authorize-hook.svg)](https://www.npmjs.com/package/@morphatic/feathers-auth0-authorize-hook)
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/morphatic/astrologyjs/master/LICENSE)

This plugin was designed to be used in scenarios where [FeathersJS](https://feathersjs.com/) is being used solely as a backend API server, and [Auth0](https://auth0.com/) is being used for authentication from a frontend client written with, e.g. Vue, React, or Angular. For a fuller discussion of this scenario and why I chose to write this plugin, [check out this blog post](https://morphatic.com/2019/04/14/authorizing-feathers-api-requests-for-vue-react-angular-apps-using-auth0/). If you are using Feathers for BOTH the backend AND the frontend, you're probably much better off using the [`@feathersjs/authentication`](https://docs.feathersjs.com/api/authentication/server.html) that's already part of the framework.

## Installation and Configuration

To install this plugin, from the root of your package:

```console
npm install --save @morphatic/feathers-auth0-authorize-hook
```

In your feathers app config (usually found in `config/default.json`) you'll need to add the following properties. You'll want to update them with your actual Auth0 domain, i.e. swap out the `example` below with the domain for your app which you can find in your [Auth0 application settings](https://manage.auth0.com/dashboard/us/qzuku/applications).

```json
{
  "jwksUri": "https://example.auth0.com/.well-known/jwks.json",
  "jwtOptions": {
    "algorithms": [
      "RS256"
    ],
    "audience": [
      "https://example.auth0.com/api/v2/",
      "https://example.auth0.com/userinfo"
    ],
    "ignoreExpiration": false,
    "issuer": "https://example.auth0.com/"
  }
}
```

Also, you will need to create or update two [services](https://crow.docs.feathersjs.com/guides/basics/services.html): `users` and `keys`. You'll also need to add a simple middleware.

### The `users` service

It's likely that you already have a `users` service in your app. If you don't you'll need to create one, e.g. by [using the `feathers-plus` generator](https://generator.feathers-plus.com/get-started/#generate-service). Assuming you already have a `users` service, you'll need to ensure that your model has two string properties: `user_id` and `currentToken`. `user_id` will hold the `user_id` generated by Auth0 when the user's account was created. `currentToken` will be used to store already-verified tokens, to prevent the app from having to re-verify tokens on every request. For reference, [a JSON Schema representation of a minimal `users` service](https://github.com/morphatic/feathers-auth0-authorize-hook/blob/master/test/user-schema.json) is included in this repo.

### The `keys` service

You'll also need to generate a `keys` service in your app. This is used to store JSON web keys (JWKs) retrieved from [Auth0's JWKS endpoint](https://auth0.com/docs/jwks). This service does not need to be persistent (I use an in-memory service) as it is mainly used to cache already-retrieved JWKs and improve performance. For reference, please see [the JSON Schema version of the `keys` service](https://github.com/morphatic/feathers-auth0-authorize-hook/blob/master/test/key-schema.json) contained in this repo.

Although not strictly necessary, since the `keys` service does not contain any non-public information, I typicaly add a hook to prevent the `keys` service from being called by external clients like this:

```js
// src/services/keys/keys.hooks.js
const { disallow } = require('feathers-hooks-common')
module.exports = {
  before: {
    all: [
      disallow('external')
    ]
  }
}
```

### The middleware

This hook relies upon extracting a JWT from the `Authorization` HTTP header (and as such ONLY works for REST transports--not socket.io/primus). By default, however, HTTP headers are not available to hooks, so you need to create middleware to make them so. One way to do this is to generate new middleware with a CLI generator and add the following code to it:

```js
// src/middleware/authorize.js
module.exports = function (options = {}) {
  return function authorize(req, res, next) {
    req.feathers = { ...req.feathers, headers: req.headers }
    next()
  }
}
```

Alternatively, you can modify `src/app.js` to modify the REST configuration section as follows:

```js
// src/app.js
app.configure(express.rest(
  (req, res, next) => {
    req.feathers = { ...req.feathers, headers: req.headers }
    next()
  }
))
```

(Personally, I prefer the former method as I like to leave the generated `app.js` file basically untouched.)

## Using the `authorize` hook

You'll need to make changes on both the server and the client to use this hook.

### On the server

Once installed you'll need to add this hook into your feathers app. You can use it either for the entire app or for individual services. I find it easiest to set it up in conjunction with [`feathers-hooks-common`](https://github.com/feathers-plus/feathers-hooks-common). This hook ONLY works as a `before` hook. Here's an example of using it for your entire app (_NOTE: I've abbreviated this to just the `before.all` hook._):

```js
// src/app.hooks.js
// import feathers-hooks-common `isProvider` and `unless`
const { isProvider, unless } = require('feathers-hooks-common')
// import the authorize hook
const { authorize } = require('@morphatic/feathers-auth0-authorize-hook')() // <-- note the parentheses

module.exports = {
  before: {
    all: [
      unless(isProvider('server'), authorize)
    ]
  }
}
```

Setting it up this way will not require internal service calls to be authorized. If you don't do this, it could make your feathers app unusable since internal functions would have no way to be authorized.

### On the client

From your frontend app, you'll need to set up any services that access your feathers API as follows. I've used this in a Vue app, but it should work equally well for any other frontend framework like React or Angular. In this example, I'm imagining that I'm developing the proverbial To Do List app, and that my To Do list items are stored in my feathers backend.

```js
// src/services/feathers.js
import feathers from '@feathersjs/client'
import axios from 'axios' // NOTE: this only works for REST clients

const app = feathers()
const rc = feathers.rest('https://api.example.com') // the URL to your feathers API server
app.configure(rc.axios(axios))

export const api = token => {
  // throw an error if no token was passed
  if (!token) throw 'Token cannot be empty!'

  // add the token to the Authorization header
  const params = {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }

  // now just pass these params with every call to the API
  return {
    getTodos: async query => {
      if (query) params.query = query
      return app.service('todos').find(params)
    }
  }
}
```

And then in the view or component that consumes the api:

```js
// src/views/ToDoList.js
import { api } from '../services/feathers'

// assuming you've already authenticated with Auth0 and stored your
// token localy, e.g. in window.sessionStorage
const token = sessionStorage.getItem('auth0_access_token')
const { getTodos } = api(token)

// get all "to do" items due in the future
const query = {
  dueDate: {
    $gt: new Date().getTime()
  }
}
getTodos(query)
  .then(items => {
    // do something with the retrieved items
  })
  .catch(err => {
    // handle any errors, including failure to authorize
  })
```

## Custom Configuration

Remember the "extra" parentheses we had to use when importing the hook into `app.hooks.js` above?

```js
// from src/app.hooks.js in our feathers app
const { authorize } = require('@morphatic/feathers-auth0-authorize-hook')() // <-- NOTE: the "extra" parentheses
```

They're necessary because the function that returns the hook uses [the factory pattern](https://medium.com/javascript-scene/javascript-factory-functions-with-es6-4d224591a8b1) and allows for customization. While you can swap out any of the functions in this plugin for your own implementation, the main things you're likely to want to customize are the services used to hold the `user_id` and `keys`.

### Using a different `users` service

By default, the hook queries the `users` service to find a `user` whose `user_id` matches the Auth0 `user_id` stored in the `sub` claim in the token. However, you might want to store this information on a different model in a different service. To do that, you'd provide an `options` object as a parameter to the `require()()` function and set the `userService` property to the name of the service you want to use. For example, pretend that instead of `users`, you want to look for the `user_id` property on the `members` service instead:

```js
// example src/app.hooks.js with custom `users` service
// first, create an `options` object with the preferred name for your service
const options = {
  userService: 'members'
}
const { authorize } = require('@morphatic/feathers-auth0-authorize-hook')(options) // <-- second, pass it to the import statement
```

That's it!

### Using a different `keys` service

Similarly, you can use a different service name for where you store and look for the JWKs. Here's an example showing customization of BOTH the `users` and `keys` services:

```js
// example src/app.hooks.js with custom `users` service
// first, create an `options` object with the preferred names for your services
const options = {
  userService: 'members',
  keysService: 'jwks'
}
const { authorize } = require('@morphatic/feathers-auth0-authorize-hook')(options) // <-- second, pass it to the import statement
```

## Comments, Questions, Issues, etc

I welcome feedback, bug reports, enhancement requests, or reports on your experiences with the plugin. Undoubtedly, there's a better way to do this than what I've come up with, and I'd love to hear about it. That being said, I hope some people will find this useful!!!