OAuth2 Authentication

GitHub stars
@feathersjs/authentication-oauth2"">npm version
Changelog

  1. $ npm install @feathersjs/authentication-oauth2 --save

@feathersjs/authentication-oauth2 is a server side module that allows you to use any Passport OAuth2 authentication strategy within your Feathers application. There are hundreds of them! Some commonly used ones are:

This module contains 2 core pieces:

  1. The main initialization function
  2. The Verifier class

Configuration

In most cases initializing the module is as simple as doing this:

  1. const feathers = require('@feathersjs/feathers');
  2. const authentication = require('@feathersjs/authentication');
  3. const jwt = require('@feathersjs/authentication-jwt');
  4. const oauth2 = require('@feathersjs/authentication-oauth2');
  5. const FacebookStrategy = require('passport-facebook').Strategy;
  6. const app = feathers();
  7. // Setup authentication
  8. app.configure(authentication({ secret: 'super secret' }));
  9. app.configure(jwt());
  10. app.configure(oauth2({
  11. name: 'facebook',
  12. Strategy: FacebookStrategy,
  13. clientID: '<your client id>',
  14. clientSecret: '<your client secret>',
  15. scope: ['public_profile', 'email']
  16. }));
  17. // Setup a hook to only allow valid JWTs to authenticate
  18. // and get new JWT access tokens
  19. app.service('authentication').hooks({
  20. before: {
  21. create: [
  22. authentication.hooks.authenticate(['jwt'])
  23. ]
  24. }
  25. });

This will pull from your global authentication object in your config file. It will also mix in the following defaults, which can be customized.

Registering the OAuth2 plugin will automatically set up routes to handle the OAuth redirects and authorization.

Options

  1. {
  2. idField: '<provider>Id', // The field to look up the entity by when logging in with the provider. Defaults to '<provider>Id' (ie. 'facebookId').
  3. path: '/auth/<provider>', // The route to register the middleware
  4. callbackURL: 'http(s)://hostname[:port]/auth/<provider>/callback', // The callback url. Will automatically take into account your host and port and whether you are in production based on your app environment to construct the url. (ie. in development http://localhost:3030/auth/facebook/callback)
  5. successRedirect: undefined,
  6. failureRedirect: undefined,
  7. entity: 'user', // the entity that you are looking up
  8. service: 'users', // the service to look up the entity
  9. passReqToCallback: true, // whether the request object should be passed to `verify`
  10. session: false, // whether to use sessions,
  11. handler: middleware, // Express middleware for handling the oauth callback. Defaults to the built in middleware.
  12. errorHandler: middleware, // Express middleware for handling errors. Defaults to the built in middleware.
  13. formatter: middleware, // The response formatter middleware. Defaults to the the built in feathers-rest formatter, handling only JSON.
  14. Verifier: Verifier // A Verifier class. Defaults to the built-in one but can be a custom one. See below for details.
  15. }

Additional passport strategy options can be provided based on the OAuth1 strategy you are configuring.

Note: If your api and frontend live on separate domains, you will need to manually setup a handler to pass the access token back to the frontend application. See this FAQ.

Verifier

This is the verification class that handles the OAuth2 verification by looking up the entity (normally a user) on a given service and either creates or updates the entity and returns them. It has the following methods that can all be overridden. All methods return a promise except verify, which has the exact same signature as passport-oauth2.

  1. {
  2. constructor(app, options) // the class constructor
  3. _updateEntity(entity) // updates an existing entity
  4. _createEntity(entity) // creates an entity if they didn't exist already
  5. _normalizeResult(result) // normalizes result from service to account for pagination
  6. verify(req, accessToken, refreshToken, profile, done) // queries the service and calls the other internal functions.
  7. }

The Verifier class can be extended so that you customize it’s behavior without having to rewrite and test a totally custom local Passport implementation. Although that is always an option if you don’t want use this plugin.

An example of customizing the Verifier:

  1. import oauth2, { Verifier } from '@feathersjs/authentication-oauth2';
  2. class CustomVerifier extends Verifier {
  3. // The verify function has the exact same inputs and
  4. // return values as a vanilla passport strategy
  5. verify(req, accessToken, refreshToken, profile, done) {
  6. // do your custom stuff. You can call internal Verifier methods
  7. // and reference this.app and this.options. This method must be implemented.
  8. // the 'user' variable can be any truthy value
  9. // the 'payload' is the payload for the JWT access token that is generated after successful authentication
  10. done(null, user, payload);
  11. }
  12. }
  13. app.configure(oauth2({
  14. name: 'facebook',
  15. Strategy: FacebookStrategy,
  16. clientID: '<your client id>',
  17. clientSecret: '<your client secret>',
  18. scope: ['public_profile', 'email'],
  19. Verifier: CustomVerifier
  20. }));

Customizing The OAuth Response

Whenever you authenticate with an OAuth2 provider such as Facebook, the provider sends back an accessToken, refreshToken, and a profile that contains the authenticated entity’s information based on the OAuth2 scopes you have requested and been granted.

By default the Verifier takes everything returned by the provider and attaches it to the entity (ie. the user object) under the provider name. You will likely want to customize the data that is returned. This can be done by adding a before hook to both the update and create service methods on your entity‘s service.

  1. app.configure(oauth2({
  2. name: 'github',
  3. entity: 'user',
  4. service: 'users',
  5. Strategy,
  6. clientID: 'your client id',
  7. clientSecret: 'your client secret'
  8. }));
  9. function customizeGithubProfile() {
  10. return function(context) {
  11. console.log('Customizing Github Profile');
  12. // If there is a github field they signed up or
  13. // signed in with github so let's pull the primary account email.
  14. if (context.data.github) {
  15. context.data.email = context.data.github.profile.emails.find(email => email.primary).value;
  16. }
  17. // If you want to do something whenever any OAuth
  18. // provider authentication occurs you can do this.
  19. if (context.params.oauth) {
  20. // do something for all OAuth providers
  21. }
  22. if (context.params.oauth.provider === 'github') {
  23. // do something specific to the github provider
  24. }
  25. return Promise.resolve(context);
  26. };
  27. }
  28. app.service('users').hooks({
  29. before: {
  30. create: [customizeGithubProfile()],
  31. update: [customizeGithubProfile()]
  32. }
  33. });

Client Usage

When this module is registered server side, whether you are using feathers-authentication-client or not the user has to navigate to the authentication strategy url. This could be by setting window.location or through a link in your app.

For example you might have a login button for Facebook:

  1. <a href="/auth/facebook" class="button">Login With Facebook</a>