/multiple-authentication-services-on-feathers-app

This is an example to show how to implement multiple authentication services on a FeathersJS app.

Primary LanguageTypeScript

multiple-authentication-services-on-feathers-app

This is an example to show how to implement multiple authentication services on a FeathersJS app.

Prerequisites

To proceed with this tutorial you must first read the FeathersJS' documentation.

Config Authentication Services

Generate 2 Authentication Services With Each Of Its Own RESTful services

feathers generate services

Let's generate 2 services called users and clients in this tutorial.

Modify Configuration File

Authentication configuration for 2 authentication services should be in 2 different key in Feathers app's configuration file.

{
  "authentication_users": {
    "entity": "user",
    "service": "users",
    "secret": "******UserAuthenticationSecrets******",
    "authStrategies": [
      "jwt",
      "local"
    ],
    "jwtOptions": {
      "header": {
        "typ": "access"
      },
      "audience": "https://yourdomain.com",
      "issuer": "feathers",
      "algorithm": "HS256",
      "expiresIn": "1d"
    },
    "local": {
      "usernameField": "email",
      "passwordField": "password"
    }
  },
  "authentication_clients": {
    "entity": "client",
    "service": "clients",
    "secret": "******ClientAuthenticationSecrets******",
    "authStrategies": [
      "jwt",
      "local"
    ],
    "jwtOptions": {
      "header": {
        "typ": "access"
      },
      "audience": "https://yourdomain.com",
      "issuer": "feathers",
      "algorithm": "HS256",
      "expiresIn": "1d"
    },
    "local": {
      "usernameField": "email",
      "passwordField": "password"
    }
  }
}

Setup Multiple Authentication Services

Authentication service will be setup in src/authentication.ts. By default, this is only 1 authentication got setup here. Let's modify this file to setup 2 authentication services here.

Customize JWT Payload

JWT are verified in Feathers Hooks, you should know that through FeathersJS documentation.

If we're to use multiple authentication services in our Feathers app, we should consider adding a field to JWT's payload to proclaim which authentication service should handle this token.

Let's do it via customizing the authentication services

class UsersAuthService extends AuthenticationService {
  // customizing JWT's payload
  async getPayload(authResult: AuthenticationResult, params: Params) {
    // Call original `getPayload` first
    const payload = await super.getPayload(authResult, params);
    const {user} = authResult;

    if (user) {
      payload.role = 'user'; // assign role as user
    }

    return payload;
  }
}

class ClientsAuthService extends AuthenticationService {
  // customizing JWT's payload
  async getPayload(authResult: AuthenticationResult, params: Params) {
    // Call original `getPayload` first
    const payload = await super.getPayload(authResult, params);
    const {client} = authResult;

    if (client) {
      payload.role = 'client'; // assign role as  client
    }

    return payload;
  }
}

Register Multiple Authentication Services

Since we already have 2 customized authentication services, let's register them in src/authentication.ts

export default function(app: Application): void {

  // Register the UsersAuthService
  const usersAuthService = new UsersAuthService(
    app,
    'authentication_users', // assign configKey so that feathers app will look for specific configuration for this authentication service
  );
  usersAuthService.register('jwt', new JWTStrategy());
  usersAuthService.register('local', new LocalStrategy());
  // Setup http path for usersAuthService
  app.use('/authentication/users', usersAuthService);

  // Register the UsersAuthService
  const clientsAuthService = new ClientsAuthService(
    app,
    'authentication_clients', // assign configKey so that feathers app will look for specific configuration for this authentication service
  );
  clientsAuthService.register('jwt', new JWTStrategy());
  clientsAuthService.register('local', new LocalStrategy());
  // Setup http path for clientsAuthService
  app.use('/authentication/clients', clientsAuthService);

  app.configure(expressOauth());
}

Route The Authentication Hook

JWT tokens must be authenticated by its own issuer(authentication service).

Let wrap the origin authenticate hook and route by token.

/**
 * authenticate hook assigned to UsersAuthService
 */
const authenticateUsers = authenticate({
    service: 'authentication/users', strategies: ['jwt']
  });

/**
 * authenticate hook assigned to ClientsAuthService
 */
const authenticateClients = authenticate({
  service: 'authentication/clients', strategies: ['jwt']
});

export const authenticateHook = async (ctx: HookContext) => {
  try {
    const token = ctx.params?.authentication?.accessToken;
    const payload = jwt.decode(token);
    // look for role in payload, 
    // so that we can know which authenticate hook handles this token.
    if (payload) {
      const {role} = payload;
      if (role === 'user') {
        return authenticateUsers(ctx);
      } else if (role === 'client') {
        return authenticateClients(ctx);
      }
    } else { // if payload is not found, we route the authenticate call by it's path, this is mostly in login situations.
      const {path} = ctx;
      if (path === 'users') {
        return authenticateUsers(ctx);
      } else if (path === 'clients') {
        return authenticateClients(ctx);
      }
    }
  } catch (e) {
    console.error(e);
  }

  // if no routing strategy matched
  throw new NotAuthenticated('Unknown authentication service.');
};

Use The Routed Authentication Hook

Let's use the routed authentication hook at where we want to authenticate multiple kinds of users.

export default {
  before: {
    all: [],
    find: [ authenticateHook ],
    get: [ authenticate('jwt') ],
    create: [ hashPassword('password') ],
    update: [ hashPassword('password'),  authenticateHook ],
    patch: [ hashPassword('password'),  authenticateHook ],
    remove: [ authenticateHook ]
  },
// ... 
}