Custom Auth Strategy ============================================ The Auk release of FeathersJS includes a powerful new :doc:`../../api/authentication/server` built on top of `PassportJS `_. The new plugins are very flexible, allowing you to customize nearly everything. We can leverage this to create completely custom authentication strategies using `Passport Custom `_. Let’s take a look at two such examples in this guide. Setting up the basic app ------------------------ Let’s first start by creating a basic server setup. .. code:: js const feathers = require('@feathersjs/feathers'); const express = require('@feathersjs/express'); const auth = require('@feathersjs/authentication'); const jwt = require('@feathersjs/authentication-jwt'); const memory = require('feathers-memory'); const app = express(feathers()); app.configure(express.rest()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.configure(auth({ secret: 'secret' })); app.configure(jwt()); app.use('/users', memory()); app.hooks({ before: { all: [auth.hooks.authenticate('jwt')] } }); app.listen(8080); Creating a Custom API Key Auth Strategy --------------------------------------- The first custom strategy example we can look at is an API Key Strategy. Within it, we’ll check if there is a specific header in the request containing a specific API key. If true, we’ll successfully authorize the request. First let’s make the strategy using `passport-custom `_ npm package. .. code:: js const Strategy = require('passport-custom'); module.exports = opts => { return function() { const verifier = (req, done) => { // get the key from the request header supplied in opts const key = req.params.headers[opts.header]; // check if the key is in the allowed keys supplied in opts const match = opts.allowedKeys.includes(key); // user will default to false if no key is present // and the authorization will fail const user = match ? 'api' : match; return done(null, user); }; // register the strategy in the app.passport instance this.passport.use('apiKey', new Strategy(verifier)); // Add options for the strategy this.passport.options('apiKey', {}); }; }; Next let’s add this to our server setup .. code:: js const apiKey = require('./apiKey'); app.configure( apiKey({ // which header to look at header: 'x-api-key', // which keys are allowed allowedKeys: ['opensesame'] }) ); Next let’s create a custom authentication hook that conditionally applies auth for all external requests. .. code:: js const commonHooks = require('feathers-hooks-common'); const authenticate = () => commonHooks.iff( // if and only if the request is external commonHooks.every(commonHooks.isProvider('external')), commonHooks.iffElse( // if the specific header is included ctx => ctx.params.headers['x-api-key'], // authentication with this strategy auth.hooks.authenticate('apiKey'), // else fallback on the jwt strategy auth.hooks.authenticate(['jwt']) ) ); app.hooks({ before: { all: [authenticate()] } }); Finally our ``server.js`` looks like this: .. code:: js const feathers = require('@feathersjs/feathers'); const express = require('@feathersjs/express'); const auth = require('@feathersjs/authentication'); const jwt = require('@feathersjs/authentication-jwt'); const memory = require('feathers-memory'); const commonHooks = require('feathers-hooks-common'); const apiKey = require('./apiKey'); const app = express(feathers()); app.configure(express.rest()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.configure(auth({ secret: 'secret' })); app.configure(jwt()); app.configure( apiKey({ header: 'x-api-key', allowedKeys: ['opensesame'] }) ); app.use('/users', memory()); const authenticate = () => commonHooks.iff( commonHooks.every(commonHooks.isProvider('external')), commonHooks.iffElse( ctx => ctx.params.headers['x-api-key'], auth.hooks.authenticate('apiKey'), auth.hooks.authenticate(['jwt']) ) ); app.hooks({ before: { all: [authenticate()] } }); app.listen(8080); Now any request with a header ``x-api-key`` and the value ``opensesame`` will be authenticated by the server. Creating an Anonymous User Strategy ----------------------------------- The second strategy we’ll look at is for an anonymous user. For this specific flow we’ll expect the client to call the ``/authentication`` endpoint letting us know that it wants to authenticate anonymously. The server will then create a new user and return a new JWT token that the client will have to use from that point onwards. First let’s create the strategy using ``passport-custom`` .. code:: js const Strategy = require('passport-custom'); module.exports = opts => { return function() { const verifier = async (req, done) => { // create a new user in the user service // mark this user with a specific anonymous=true attribute const user = await this.service(opts.userService).create({ anonymous: true }); // authenticate the request with this user return done(null, user, { userId: user.id }); }; // register the strategy in the app.passport instance this.passport.use('anonymous', new Strategy(verifier)); }; }; Next let’s update our ``server.js`` to use this strategy. .. code:: js const anonymous = require('./anonymous'); app.configure( anonymous({ // the user service userService: 'users' }) ); const authenticate = () => commonHooks.iff( commonHooks.every(commonHooks.isProvider('external')), commonHooks.iffElse( ctx => ctx.params.headers['x-api-key'], auth.hooks.authenticate('apiKey'), // add the additional anonymous strategy auth.hooks.authenticate(['jwt', 'anonymous']) ) ); Finally our ``server.js`` looks like this: .. code:: js const feathers = require('@feathersjs/feathers'); const express = require('@feathersjs/express'); const auth = require('@feathersjs/authentication'); const jwt = require('@feathersjs/authentication-jwt'); const memory = require('feathers-memory'); const commonHooks = require('feathers-hooks-common'); const apiKey = require('./apiKey'); const anonymous = require('./anonymous'); const app = express(feathers()); app.configure(express.rest()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.configure(auth({ secret: 'secret' })); app.configure(jwt()); app.configure( apiKey({ header: 'x-api-key', allowedKeys: ['opensesame'] }) ); app.configure( anonymous({ userService: 'users' }) ); app.use('/users', memory()); const authenticate = () => commonHooks.iff( commonHooks.every(commonHooks.isProvider('external')), commonHooks.iffElse( ctx => ctx.params.headers['x-api-key'], auth.hooks.authenticate('apiKey'), auth.hooks.authenticate(['jwt', 'anonymous']) ) ); app.hooks({ before: { all: [authenticate()] } }); app.listen(8080); Now any such request will return a valid JWT token: .. code:: js POST /authentication { strategy: 'anonymous' } .. note:: this looks very similar to a request body for ``local`` strategy: .. code:: js POST /authentication { strategy: 'local', username: 'admin', password: 'password' } So for any new strategy we register, we can call the ``/authentication`` endpoint with a specific body and expect a valid JWT in return, which we can use from thereon. -------------- As we can see it’s very easy to create a completely custom auth strategy in a standard passport way using ``passport-custom``. Happy Hacking!!