At some point, you are probably going to put information in your databases that you want to keep private. You'll need to implement an authentication scheme to identify your users, and authorization to control access to resources. The feathers-authentication plugin makes it easy to add token-based auth to your app.
Cookie based and token based authentication are the two most common methods of putting server side authentication into practice. Cookie based authentication relies on server side cookies to remember the user. Token based authentication requires an encrypted auth token with each request. While cookie based authentication is the most common, token based authentication offers several advantages for modern web apps. Two primary advantages are security and scalability.
The Auth0 blog has a great article on the advantages that token authentication offers.
If you are using the default options, setting up JWT auth for your Feathers app is as simple as the example below.
let app = feathers()
.configure(rest())
.configure(socketio())
.configure(hooks())
.use(bodyParser.json())
.use(bodyParser.urlencoded({ extended: true }))
.configure(authentication());ProTip: You must set up the
body-parser,feathers-hooksand possiblycorsmodules before setting upfeathers-authentication. The Feathers generator does this for you already.
The options passed to the authentication plugin are wrapped in an object with the following top level keys:
local(default: see options) [optional] - The local auth provider config. By default this is included in a Feathers app. If set tofalseit will not be initialized.token(default: see options) [optional] - The JWT auth provider config. By default this is included in a Feathers app even if you don't pass in any options. You can't disable it likelocalauth.<oauth-provider>(default: see options) [optional] - A lowercased oauth provider name (ie.facebookorgithub)successRedirect(default: '/auth/success') [optional] - The endpoint to redirect to after successful authentication or signup. Only for requests not over Ajax or sockets.failureRedirect(default: '/auth/failure') [optional] - The endpoint to redirect to for a failed authentication or signup. Only for requests not over Ajax or sockets.userEndpoint(default: '/users') [optional] - The user service endpointtokenEndpoint(default: '/auth/token') [optional] - The JWT auth service endpointheader(default: 'authorization') [optional] - The header field to check for the token. This is case sensitive.cookie(default: 'feathers-jwt') [optional] - The cookie field to check for the token. This is case sensitive.idField(default: '_id') [optional] - the id field for you user's id. This is primarily used by thepopulateUserhook after a JWT is created by the token service.
Below is an example config providing some common override options:
{
local: {
userEndpoint: '/api/users',
usernameField: 'username'
},
token: {
secret: 'shhh secrets'
},
facebook: {
strategy: FacebookStrategy,
'clientID': 'your facebook client id',
'clientSecret': 'your facebook client secret',
'permissions': {
authType: 'rerequest',
'scope': ['public_profile', 'email']
}
}
}If you are using the default options setting up Feathers authentication client side is a breeze.
// Set up socket.io
const host = 'http://localhost:3030';
let socket = io(host, {
transport: ['websockets']
});
// Set up Feathers client side
let app = feathers()
.configure(feathers.socketio(socket))
.configure(hooks())
.configure(authentication());
// Wait for socket connection
app.io.on('connect', function(){
// Authenticate. Normally you'd grab these from a login form rather than hard-coding them
app.authenticate({
type: 'local',
'email': 'admin@feathersjs.com',
'password': 'admin'
}).then(function(result){
console.log('Authenticated!', result);
}).catch(function(error){
console.error('Error authenticating!', error);
});
});ProTip: You can also use Primus or a handful of Ajax providers over REST instead of Socket.io. Check out the Feathers client authentication section for more detail.
ProTip: You can only have one provider client side per "app". For example if you want to use both Socket.io and a REST Ajax provider then you need to configure two apps.
tokenEndpoint(default: '/auth/token') [optional] - The JWT auth service endpoint.localEndpoint(default: '/auth/local') [optional] - The local auth service endpointheader(default: 'Authorization') [optional] - The header field to set the token. This is case sensitive.cookie(default: 'feathers-jwt') [optional] - The cookie field to check for the token. This is case sensitive.
Below is an example config providing some common override options:
app.configure(authentication({
tokenEndpoint: '/token',
localEndpoint: '/login',
header: 'X-Authorization',
cookie: 'app-token'
}));Regardless of whether you use OAuth, a token, or email + password even if you don't use the client side piece, after successful login the feathers-authentication plugin gives back an encrypted JSON web token containing the user id as the payload and the user object associated with that id.
// successful auth response
{
token: 'your encrypted json web token',
data: {
email: 'hulk@hogan.net'
}
}For REST the token needs to be passed with each request. Therefore if you did .configure(rest()) in your Feathers app, the auth plugin also includes a special middleware that ensures that a token, if sent, is available on the Feathers params object by setting it on req.feathers.token.
ProTip: The
req.feathersobject is special because its attributes are made available inside Feathers hooks on thehook.paramsobject.
This middleware uses graceful fall-back to check for a token in order from most secure/appropriate to least secure:
- Authorization header (recommended)
- Cookie
- The request body
- The query string (not recommended but supported)
So you can send your token using any of those methods. Using the Authorization header it should look like this:
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IklseWEgRmFkZWV2IiwiYWRtaW4iOnRydWV9.YiG9JdVVm6Pvpqj8jDT5bMxsm0gwoQTOaZOLI-QfSNc
ProTip: The
Bearerpart can be omitted and the case doesn't matter.
ProTip: You can use a custom header name for your token by passing a
headeroption as described above.
After a socket is connected an authenticate event needs to be emitted from the client to initiate the socket authentication. The data passed with it can either be a username and password, a JWT or OAuth access tokens. After successful authentication an authenticated event is emitted from the server and just like with REST you get back a JWT and the current user. From then on you are now using an authenticated socket until that socket is disconnected, the token expires, or you log out.
Regardless of the mechanism the person used to authenticate and no matter if it was over sockets or REST, the high level order of execution is as follows:
- The credentials passed in are verified or you go through a series of OAuth redirects and are verified by the OAuth provider.
- Once credentials are verified the user associated with those credentials is looked up and if a user does not exist they are created by calling your
userservice. - The user id is encrypted into the JWT by an asymmetric hashing function using your
secretinside thetokenservice. - The user is added to the response after a token is created using the
populateUser()hook and the new token and user are returned to the client.
Regardless of the protocol, once a valid auth token as been returned to the client, for any subsequent request the token (if present) is normalized and the [verifyToken()](../authorization/bundled-hooks.md#verifyToken) hook should be called by you prior to any restricted service methods.
This hook decrypts the token found in hook.params.token. After the JWT is decrypted, the [populateUser()](../authorization/bundled-hooks.md#populateUser) hook should be called. This is used to look up the user by id in the database and attach them to hook.params.user so that any other hooks in the chain can reference the current user, for example the [requireAuth()](../authorization/bundled-hooks.md#requireAuth) hook.
For more information on refer to the Authorization chapter.
Adding authentication allows you to know who users are. Now you'll want to specify what they can do. This is done using authorization hooks. Learn more about it in the Authorization section or dive deeper into each of the individual authentication schemes:
- Setting Up Local Auth (username and password)
- Setting Up Token Auth (JWT)
- Setting Up OAuth1 (Twitter)
- Setting Up OAuth2 (Facebook, LinkedIn, etc.)
- Setting Up 2 Factor
- Auth with Feathers client