Create Endpoints with Mixed Auth
========================================================
The Auk release of FeathersJS includes a powerful new :doc:`../../api/authentication/server` built on top of
`PassportJS `_. It can be customized to
handle almost any app’s authentication requirements. In this guide,
we’ll look at how to handle a fairly common auth scenario: Sometimes an
endpoint needs to serve different information depending on whether the
user is authenticated. An unauthenticated user might only see public
records. An authenticated user might be able to see additional records.
Setup the Authentication Endpoint
---------------------------------
To get started, we need a working authentication setup. Below is a
default configuration and ``authentication.js``. They contain the same
code generated by the
`feathers-cli `_. You can create the
below setup using the following terminal commands:
1. ``npm install -g @feathersjs/cli``\
2. ``mkdir feathers-demo-local; cd feathers-demo-local``\ or a folder
name you prefer.
3. ``feathers generate app``\ use the default prompts.
4. ``feathers generate authentication``
- Select ``Username + Password (Local)`` when prompted for a
provider.
- Select the defaults for the remaining prompts.
**config/default.json:**
.. code:: json
{
"host": "localhost",
"port": 3030,
"public": "../public/",
"paginate": {
"default": 10,
"max": 50
},
"authentication": {
"secret": "99294186737032fedad37dc2e847912e1b9393f44a28101c986f6ba8b8bc0eaab48b5b4c5178f55164973c76f8f98f2523b860674f01c16a23239a2e7d7790ae9fa00b6de5cc0565e335c6f05f2e17fbee2e8ea0e82402959f1d58b2b2dc5272d09e0c1edf1d364e9911ecad8172bdc2d41381c9ab319de4979c243925c49165a9914471be0aa647896e981da5aec6801a6dccd1511da11b696d4f6cce3a4534dab9368661458a466661b1e12170ad21a4045ce1358138caf099fbc19e05532336b5626aa376bc158cf84c6a7e0e3dbbb3af666267c08de12217c9b55aea501e5c36011779ee9dd2e061d0523ddf71cb1d68f83ea5bb1299ca06003b77f0fc69",
"strategies": [
"jwt",
"local"
],
"path": "/authentication",
"service": "users",
"jwt": {
"header": {
"typ": "access"
},
"audience": "https://yourdomain.com",
"subject": "anonymous",
"issuer": "feathers",
"algorithm": "HS256",
"expiresIn": "1d"
},
"local": {
"entity": "user",
"service": "users",
"usernameField": "email",
"passwordField": "password"
}
},
"nedb": "../data"
}
**src/authentication.js:**
.. code:: js
'use strict';
const authentication = require('@feathersjs/authentication');
const jwt = require('@feathersjs/authentication-jwt');
const local = require('@feathersjs/authentication-local');
module.exports = function () {
const app = this;
const config = app.get('authentication');
app.configure(authentication(config));
app.configure(jwt());
app.configure(local(config.local));
app.service('authentication').hooks({
before: {
create: [
authentication.hooks.authenticate(config.strategies)
],
remove: [
authentication.hooks.authenticate('jwt')
]
}
});
};
Set up a “Mixed Auth” Endpoint
------------------------------
Now we need to setup an endpoint to handle both unauthenticated and
authenticated users. For this example, we’ll use the ``/users`` service
that was already created by the authentication generator. Let’s suppose
that our application requires that each ``user`` record will contain a
``public`` boolean property. Each record will look something like this:
.. code:: js
{
id: 1,
email: 'my@email.com'
password: "password",
public: true
}
If a ``user`` record contains ``public: true``, then **unauthenticated**
users should be able to access it. Let’s see how to use the ``iff`` and
``else`` conditional hooks from
`feathers-hooks-common `_
to make this happen. Be sure to read the
`iff hook API docs `_
and
`else hook API docs `_
if you haven’t, yet.
We’re going to use the ``iff`` hook to authenticate users only if a
token is in the request. The :doc:`../../api/authentication/jwt`. which we used in
``src/authentication.js``, includes a token extractor. If a request
includes a token, it will automatically be available inside the
``context`` object at ``context.params.token``.
**src/services/users/users.hooks.js**\ (This example only shows the
``find`` method’s ``before`` hooks.)
.. code:: js
'use strict';
const { authenticate } = require('@feathersjs/authentication').hooks;
const commonHooks = require('feathers-hooks-common');
module.exports = {
before: {
find: [
// If a token was included, authenticate it with the `jwt` strategy.
commonHooks.iff(
context => context.params.token,
authenticate('jwt')
// No token was found, so limit the query to include `public: true`
).else( context => Object.assign(context.params.query, { public: true }) )
]
}
};
Let’s break down the above example. We setup the ``find`` method of the
``/users`` service with an ``iff`` conditional before hook:
.. code:: js
iff(
context => context.params.token,
authenticate(‘jwt’)
)
For this application, the above snippet is equivalent to the snippet,
below.
.. code:: js
context => {
if (context.params.token) {
return authenticate(‘jwt’)
} else {
return Promise.resolve(context)
}
}
The ``iff`` hook is actually more capable than the simple demonstration,
above. It can handle an async predicate expression. This would be
equivalent to being able to pass a ``promise`` inside the ``if``
statement’s parentheses. It also allows us to chain an \ ``.else()``
statement, which will run if the predicate evaluates to false.
.. code:: js
.else(
context => {
Object.assign(context.params.query, { public: true })
return context
)
The above statement simply adds ``public: true`` to the query
parameters. This limits the query to only find ``user`` records that
have the ``public`` property set to ``true``.
Wrapping Up
-----------
With the above code, we’ve successfully setup a ``/users`` service that
responds differently to unauthenticated and authenticated users. We used
the ``context.params.token`` attribute to either authenticate a user or
to limit the search query to only public users. If you become familiar
with the `Common Hooks
API `_,
you’ll be able to solve almost any authentication puzzle.