Skip to content
On this page

Pre-release

You are looking at the website for the fully functional Feathers v5 (Dove) pre-release. Check out what's new, and please let us know about any issues or questions . The current v4 documentation can be found at crow.docs.feathersjs.com.

Authentication

We now have a fully functional chat application consisting of services and schemas. The services come with authentication enabled by default, so before we can use it we need to create a new user and learn how Feathers authentication works. We will look at authenticating our REST API, and then how to authenticate with Feathers in the browser. Finally we will implement a "Login with GitHub" button.

Registering a user

The HTTP REST API can be used directly to register a new user. We can do this by sending a POST request to http://localhost:3030/users with JSON data like this as the body:

js
// POST /users
{
  "email": "hello@feathersjs.com",
  "password": "supersecret"
}
// POST /users
{
  "email": "hello@feathersjs.com",
  "password": "supersecret"
}

Try it:

sh
curl 'http://localhost:3030/users/' \
  -H 'Content-Type: application/json' \
  --data-binary '{ "email": "hello@feathersjs.com", "password": "supersecret" }'
curl 'http://localhost:3030/users/' \
  -H 'Content-Type: application/json' \
  --data-binary '{ "email": "hello@feathersjs.com", "password": "supersecret" }'

Run in Postman

info

For SQL databases, creating a user with the same email address will only work once, then fail since it already exists. With the default database selection, you can reset your database by removing the feathers-chat.sqlite file and running npm run migrate to initialise it again.

This will return something like this:

json
{
  "id": 123,
  "email": "hello@feathersjs.com",
  "avatar": "https://s.gravatar.com/avatar/ffe2a09df37d7c646e974a2d2b8d3e03?s=60"
}
{
  "id": 123,
  "email": "hello@feathersjs.com",
  "avatar": "https://s.gravatar.com/avatar/ffe2a09df37d7c646e974a2d2b8d3e03?s=60"
}

Which means our user has been created successfully.

info

The password has been encrypted and stored securely in the database but will never be included in an external response.

Logging in

By default, Feathers uses JSON Web Tokens for authentication. It is an access token that is issued by the Feathers server for a limited time (one day by default) and needs to be sent with every API request that requires authentication. Usually a token is issued for a specific user, Let's see if we can issue a JWT for the user that we just created.

tip

If you are wondering why Feathers is using JWT for authentication, have a look at this FAQ.

Tokens can be created by sending a POST request to the /authentication endpoint (which is the same as calling the create method on the authentication service set up in src/authentication) and passing the authentication strategy you want to use along with any other relevant data. To get a token for an existing user through a username (email) and password login, we can use the built-in local authentication strategy with a request like this:

js
// POST /authentication
{
  "strategy": "local",
  "email": "hello@feathersjs.com",
  "password": "supersecret"
}
// POST /authentication
{
  "strategy": "local",
  "email": "hello@feathersjs.com",
  "password": "supersecret"
}

Try it:

sh
curl 'http://localhost:3030/authentication/' \
  -H 'Content-Type: application/json' \
  --data-binary '{ "strategy": "local", "email": "hello@feathersjs.com", "password": "supersecret" }'
curl 'http://localhost:3030/authentication/' \
  -H 'Content-Type: application/json' \
  --data-binary '{ "strategy": "local", "email": "hello@feathersjs.com", "password": "supersecret" }'

Run in Postman

This will return something like this:

json
{
  "accessToken": "<JWT for this user>",
  "authentication": {
    "strategy": "local"
  },
  "user": {
    "id": 123,
    "email": "hello@feathersjs.com",
    "avatar": "https://s.gravatar.com/avatar/ffe2a09df37d7c646e974a2d2b8d3e03?s=60"
  }
}
{
  "accessToken": "<JWT for this user>",
  "authentication": {
    "strategy": "local"
  },
  "user": {
    "id": 123,
    "email": "hello@feathersjs.com",
    "avatar": "https://s.gravatar.com/avatar/ffe2a09df37d7c646e974a2d2b8d3e03?s=60"
  }
}

The accessToken can now be used for other REST requests that require authentication by sending the Authorization: Bearer <accessToken> HTTP header. For example to create a new message:

sh
curl 'http://localhost:3030/messages/' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <accessToken>' \
  --data-binary '{ "text": "Hello from the console" }'
curl 'http://localhost:3030/messages/' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <accessToken>' \
  --data-binary '{ "text": "Hello from the console" }'

tip

Make sure to replace the <accessToken> in the above request. For more information about the direct usage of the REST API see the REST client API and for websockets the Socket.io client API.

That's pretty neat, but emails and passwords are boring, let's spice things up a bit.

Login with GitHub

OAuth is an open authentication standard supported by almost every major social platform and what gets us the log in with Facebook, Google, GitHub etc. buttons. From the Feathers perspective, the authentication flow with OAuth is pretty similar. Instead of authenticating with the local strategy by sending a username and password, Feathers directs the user to authorize the application with the login provider. If it is successful, Feathers authentication finds or creates the user in the users service with the information it got back from the provider and then issues a token for them.

To allow login with GitHub, we first have to create a new OAuth application on GitHub. You can put anything in the name, homepage and description fields. The callback URL must be set to

sh
http://localhost:3030/oauth/github/callback
http://localhost:3030/oauth/github/callback

Screenshot of the GitHub application screen

info

You can find your existing applications in the GitHub OAuth apps developer settings.

Once you've clicked "Register application", we need to update our Feathers app configuration with the client id and secret copied from the GitHub application settings.

Find the authentication section in config/default.json replace the <Client ID> and <Client Secret> in the github section with the proper values:

js
{
  "authentication": {
    "oauth": {
      "github": {
        "key": "<Client ID>",
        "secret": "<Client Secret>"
      }
    },
    // Other authentication configuration is here
    // ...
  }
}
{
  "authentication": {
    "oauth": {
      "github": {
        "key": "<Client ID>",
        "secret": "<Client Secret>"
      }
    },
    // Other authentication configuration is here
    // ...
  }
}

note

In a production environment you would set those values as custom environment variables.

This tells the OAuth strategy to redirect back to our index page after a successful login and already makes a basic login with GitHub possible. Because of the changes we made in the users service in the services chapter we do need a small customization though. Instead of only adding githubId to a new user when they log in with GitHub we also include their email and the avatar image from the profile we get back. We can do this by extending the standard OAuth strategy and registering it as a GitHub specific one and overwriting the getEntityData method:

Update src/authentication.ts as follows:

ts
import type { Params } from '@feathersjs/feathers'
import { AuthenticationService, JWTStrategy } from '@feathersjs/authentication'
import { LocalStrategy } from '@feathersjs/authentication-local'
import { oauth, OAuthStrategy } from '@feathersjs/authentication-oauth'
import type { OAuthProfile } from '@feathersjs/authentication-oauth'
import type { Application } from './declarations'

declare module './declarations' {
  interface ServiceTypes {
    authentication: AuthenticationService
  }
}

class GitHubStrategy extends OAuthStrategy {
  async getEntityData(profile: OAuthProfile, existing: any, params: Params) {
    const baseData = await super.getEntityData(profile, existing, params)

    return {
      ...baseData,
      // The GitHub profile image
      avatar: profile.avatar_url,
      // The user email address (if available)
      email: profile.email
    }
  }
}

export const authentication = (app: Application) => {
  const authentication = new AuthenticationService(app)

  authentication.register('jwt', new JWTStrategy())
  authentication.register('local', new LocalStrategy())
  authentication.register('github', new GitHubStrategy())

  app.use('authentication', authentication)
  app.configure(oauth())
}
import type { Params } from '@feathersjs/feathers'
import { AuthenticationService, JWTStrategy } from '@feathersjs/authentication'
import { LocalStrategy } from '@feathersjs/authentication-local'
import { oauth, OAuthStrategy } from '@feathersjs/authentication-oauth'
import type { OAuthProfile } from '@feathersjs/authentication-oauth'
import type { Application } from './declarations'

declare module './declarations' {
  interface ServiceTypes {
    authentication: AuthenticationService
  }
}

class GitHubStrategy extends OAuthStrategy {
  async getEntityData(profile: OAuthProfile, existing: any, params: Params) {
    const baseData = await super.getEntityData(profile, existing, params)

    return {
      ...baseData,
      // The GitHub profile image
      avatar: profile.avatar_url,
      // The user email address (if available)
      email: profile.email
    }
  }
}

export const authentication = (app: Application) => {
  const authentication = new AuthenticationService(app)

  authentication.register('jwt', new JWTStrategy())
  authentication.register('local', new LocalStrategy())
  authentication.register('github', new GitHubStrategy())

  app.use('authentication', authentication)
  app.configure(oauth())
}
import { AuthenticationService, JWTStrategy } from '@feathersjs/authentication'
import { LocalStrategy } from '@feathersjs/authentication-local'
import { oauth, OAuthStrategy } from '@feathersjs/authentication-oauth'

class GitHubStrategy extends OAuthStrategy {
  async getEntityData(profile, existing, params) {
    const baseData = await super.getEntityData(profile, existing, params)

    return {
      ...baseData,
      // The GitHub profile image
      avatar: profile.avatar_url,
      // The user email address (if available)
      email: profile.email
    }
  }
}

export const authentication = (app) => {
  const authentication = new AuthenticationService(app)

  authentication.register('jwt', new JWTStrategy())
  authentication.register('local', new LocalStrategy())
  authentication.register('github', new GitHubStrategy())

  app.use('authentication', authentication)
  app.configure(oauth())
}
import { AuthenticationService, JWTStrategy } from '@feathersjs/authentication'
import { LocalStrategy } from '@feathersjs/authentication-local'
import { oauth, OAuthStrategy } from '@feathersjs/authentication-oauth'

class GitHubStrategy extends OAuthStrategy {
  async getEntityData(profile, existing, params) {
    const baseData = await super.getEntityData(profile, existing, params)

    return {
      ...baseData,
      // The GitHub profile image
      avatar: profile.avatar_url,
      // The user email address (if available)
      email: profile.email
    }
  }
}

export const authentication = (app) => {
  const authentication = new AuthenticationService(app)

  authentication.register('jwt', new JWTStrategy())
  authentication.register('local', new LocalStrategy())
  authentication.register('github', new GitHubStrategy())

  app.use('authentication', authentication)
  app.configure(oauth())
}

info

For more information about the OAuth flow and strategy see the OAuth API documentation.

To log in with GitHub, visit

http://localhost:3030/oauth/github
http://localhost:3030/oauth/github

It will redirect to GitHub and ask to authorize our application. If everything went well, we get redirected to our homepage with the Feathers logo with the token information in the location hash. This will be used by the Feathers authentication client to authenticate our user.

What's next?

Sweet! We now have an API that can register new users with email/password and GitHub. This means we have everything we need for a frontend for our chat application. See the JavaScript frontend guide on how to create a plain JavaScript chat application.

Released under the MIT License.