Feathers is an API framework for Node.js, React Native and the browser that provides a combination of design patterns and tools to easily create and use REST and real-time APIs. You can read about why I’m a big fan of Feathers in my previous post.
In this post I will walk through the basics of how authentication in Feathers works. The goal of this post is to help you build a better mental model of how the different pieces fit together. After reading this, you should feel more comfortable implementing, using and customising authentication in your Feathers applications.
We’ll be looking at the upcoming Feathers Crow (V4). Rewriting authentication was a big focus for V4. Feathers team has managed to simplify and improve the authentication making it more powerful and easier to use and customise. Let’s dive in.
Packages
Feathers authentication is split across the following packages:
- @feathersjs/authentication
- @feathersjs/authentication-local
- @feathersjs/authentication-oauth
- @feathersjs/authentication-client
Which packages you’ll need will depend on your use case. Let’s look at what each one is used for.
@feathersjs/authentication
This core authentication package provides the following key features.
Authentication service. Services in Feathers contain the business logic and are accessed via http/socket APIs. The authentication service exposes an /authentication
endpoint that can be used to exchange credentials to a jwt token or to authenticate a socket connection. This service can also be used to perform lower lever operations if necessary, such as creating and verifying access tokens:
app.service('authentication').createAccessToken()
app.service('authentication').verifyAccessToken(token)
JWT strategy – strategies are used by Feathers authentication service to process incoming requests and fetch the user that is logged in. JWT strategy in particular is used to process JWT authentication tokens, as opposed to say username/password where the local strategy would be used.
Authentication hook – this is a hook you’ll use in your own services to ensure only authenticated users get access.
@feathersjs/authentication-local
This package provides the local strategy. You would use this strategy if you’d like users to be able to login with username and password.
@feathersjs/authentication-oauth
This packages provides the OAuth strategy. You would use it if you want your users to sign in with third party services, such as Google or GitHub. Feathers is using Grant under the hood to achieve this and Grant supports 180+ services out of the box. You can also configure custom providers as long as they implement OAuth 2.0, OpenID Connect and OAuth 1.0a authentication flows.
This package also provides an express middleware that has to be used to handle the OAuth redirect flow, that is handling routes such as /oauth/google
and /oauth/google/callback
.
@feathersjs/authentication-client
This is what you’d use in the client side in case you’re using the @feathersjs/feathers
client to interact with your API. This is optional as you could just as well pass the JWT token via header directly with each request. However, the authentication client is helps by providing high level methods such as authenticate()
and logout()
and handles access token storage and socket reauthentication.
To recap:
- authentication service is what exposes the authentication API via
/authentication
endpoint. - jwt, local and oauth strategies are used internally by the authentication service to handle the different authentication flows.
- authentication hook is used in your own services to ensure only authenticated users get access and to resolve
context.params.user
that can be used for permissioning among other things. - oauth middleware creates OAuth API endpoints such as the
/oauth/google/callback
url.
Usage
Now let’s look at how it all comes together. Create an auth.js
file:
const authentication = require('@feathersjs/authentication')
const authenticationLocal = require('@feathersjs/authentication-local')
const authenticationOauth = require('@feathersjs/authentication-oauth')
const { AuthenticationService, JWTStrategy } = authentication
const { LocalStrategy } = authenticationLocal
const { express: oauth, OAuthStrategy } = authenticationOauth
module.exports = function auth(app) {
const authentication = new AuthenticationService(app)
// register all of the strategies with authentication service
authentication.register('local', new LocalStrategy())
authentication.register('jwt', new JWTStrategy())
authentication.register('google', new OAuthStrategy())
// register the authentication service with your app
app.use('/api/authentication', authentication)
// register the oauth middleware with your app
app.configure(oauth())
}
And plug this into your Feathers application:
const feathers = require('@feathersjs/feathers')
const express = require('@feathersjs/express')
const auth = require('./auth')
const app = express(feathers())
app.configure(auth)
For this all to work, you’ll need a few other bits. I won’t go into detail on those, but here are some relevant code snippets:
Flows
Once everything’s in place, you should now be able to:
- sign up
- login/logout using email and password
- login/logout using “Sign in using X” button
- protect and access protected endpoints
Let’s look at how to perform these actions and how they work behind the scenes.
User sign up
To sign a user up, all you need to do is create one using the users service. This would usually be done by collecting sign up details in some form and sending them to the server using Feathers client.
feathers.service('users').create({
name: 'Some User',
email: 'some@user.com',
password: 'corvus macrorhynchos'
})
This inserts the user object into your database and hashes the password using the hashPassword
hook from the @feathersjs/authentication-local
package (if you’ve used that hook in your users service).
Notice that we don’t really use the authentication service in this flow, that is only used when signing in or accessing protected endpoints.
Signing in with username/password
Now that the user is signed up the authentication service can be used to exchange the credentials to a jwt token.
const authentication = feathers.service('authentication')
const { accessToken, user } = await authentication.create({
strategy: 'local',
email: 'some@user.com',
password: 'corvus macrorhynchos'
})
You’d store the accessToken in local storage to reuse later and you can store the user in your application’s state.
In practise, you’d likely use the Feathers authentication client instead of directly calling the authentication service. The feathers authentication client takes care of storing the token and reauthenticating web sockets upon reconnect in case of dropped connections.
const { user } = await feathers.authenticate({
strategy: 'local',
email: 'some@user.com',
password: 'corvus macrorhynchos'
})
What happens on the server when we do this? The authentication service fetches the local strategy we’ve registered in the setup and calls strategy.authenticate
method, which in turn runs through the following lifecycle:
findEntity
resolves the user object by using the username/emailcomparePassword
checks that the password is correctgetEntity
is finally used to refetch a clean user object that’s suitable for sending to the client
Once all of the steps succeed, the authentication service generates a jwt token and sends back the { accessToken, user, authentication }
to the client. If you need to customise how any of the local authentication lifecycle steps are performend, you can extend the LocalStrategy and override any of it’s methods. See Feathers docs for a customisation example.
Signing in using OAuth
The OAuth flow is a bit more involved, but Feathers handles most of it for you. Registering the express oauth
middleware earlier in our auth.js
module created the required OAuth endpoints: /oauth/google
and /oauth/google/callback
among a few others.
To sign someone in using a third party, redirect the user to /oauth/:provider
, e.g.:
export default function SignInWithGoogle() {
const onClick = () => {
window.location = '/oauth/google'
}
return <Button onClick={onClick}>Sign in with Google</Button>
}
Opening /oauth/google
will kick off the following redirect flow:
/oauth/google
- initiate the sign in with the third party provider/oauth/connect/google
- internal redirect from Feathers to Granthttps://accounts.google.com/o/oauth2/auth
- user is signing in / confirming account on Google/oauth/google/callback
- we’re back in our app with the right query params/oauth/connect/google/callback
- internal redirect from Feathers to Grant/oauth/google/authenticate
- redirect back to Feathers/auth/store
- final destination, this url would be whatever you’ve configured in yourauthentication.oauth.redirect
Once you’re redirected all the way to the final destination, you would store the access_token
from the query param, or display the error in case the authentication failed, e.g.:
/auth/store?access_token=feathers-jwt-access-token
/auth/store?error=boom
Calling feathers.authenticate()
will automatically extract these parameters from the URL, store the token in local storage and authenticate the current connection returning the usual { accessToken, user, authentication }
result object.
Accessing services
Once you’ve acquired a jwt token and it’s safely stored away for all your future sessions you can finally interact with your protected APIs.
You could pass the token with every request to Feathers in the Authorisation
header. For example, with curl, it would look like this:
curl http://localhost:3030/api/notes -H "Authorization: Bearer YOUR-TOKEN"
However, with Feathers client the authentication token is handled for you behind the scenes in a transport independent way (that is it will work with HTTP and WebSockets):
// assuming you've called feathers.authenticate() on page load
// all subsequent service calls will pass along the token as required
feathers.service('notes').find()
What happens on the server when we do this?
- In case you’re using HTTP, the Feathers REST middleware will call
parse
on authentication service, which will callparse
on jwt strategy, to extract the jwt token from the headers. And in case you’re using WebSockets, the socket would have already been previously authenticated by thefeathers.authenticate()
call. - The service you’re accessing would be protected by the
authentication('jwt')
hook. This authentication hook calls theauthenticate
method on the authentication service, which in this case callsauthenticate
on the jwt strategy. - The jwt strategy runs the following methods to extract the user from the token. First
verifyAccessToken
to make sure the token is valid and thengetEntity
to resolve a fresh user object. - Finally, the authentication hook sets
context.params.user
to the resolved user andcontext.params.authenticated
to true.
Other flows
You might want to perform other flows such as sending password reset email and then resetting user’s password. That is not something that Feathers provides out of the box. However, you can implement such flows yourself. For example, to create a password reset flow, you’d do something like the following:
- Create an
accounts
service to house the bussiness logic of your custom flows. - Execute
feathers.service('accounts').create({ action: 'send-password-reset', email: 'some@user.com' })
. - On the server, generate a random secret, hash it using
hashPassword
hook and store it in the users table. - Send user an email with a link back to your app with the secret in the url.
- Once the user opens the link, read it in your client side code and render a password reset form.
- Execute
feathers.service('accounts').create({ action: 'reset-password', secret: 'secret-from-url', password: 'new-password' })
. - On the server, check that the secret is valid, update user’s password.
- Sign the user in using
feathers.authenticate({ strategy: 'local', username, password })
.
I hope walking through all the different pieces that make up Feathers authentication helped you gain a better understanding of how it all fits together. The goal was to help you feel more comfortable when customising the authentication for your needs. In a future post, we’ll look at how to implement longer user sessions using a form of refresh tokens.
As always, for more details have a look at the official authentication docs. The docs include detailed API documentation for all of the authentication packages and methods.
If you have any questions hit me up on Twitter.