feathers icon indicating copy to clipboard operation
feathers copied to clipboard

Authenticate another service v4

Open hammooooody opened this issue 5 years ago • 5 comments

I have authentication for 'users' service and I'd like to authenticate 'systems' (which is another service I generated) with both local and jwt strategies.

how do I go about doing that?

I was able to do that with v3 by modifying default.json as I did in #1078

I am failing to do that with v4 Kindly assist!

System configuration

default.json

{
  "host": "0.0.0.0",
  "port": 3030,
  "public": "../public/",
  "paginate": {
    "default": 10,
    "max": 50
  },
  "authentication": {
    "entity": "user",
    "service": "users",
    "secret": "<secret>",
    "authStrategies": ["jwt", "local", "system"],
    "jwtOptions": {
      "header": {
        "typ": "access"
      },
      "audience": "https://yourdomain.com",
      "issuer": "feathers",
      "algorithm": "HS256",
      "expiresIn": "1d"
    },
    "local": {
      "name": "local",
      "usernameField": "email",
      "passwordField": "password"
    },
    "system": {
      "name": "system",
      "entity": "system",
      "service": "systems",
      "usernameField": "sysUsername",
      "passwordField": "sysPassword"
    }
  },

  "mongodb": "<url>"
}

Module versions:

 "dependencies": {
    "@feathersjs/authentication": "^4.3.10",
    "@feathersjs/authentication-local": "^4.3.10",
    "@feathersjs/authentication-oauth": "^4.3.10",
    "@feathersjs/configuration": "^4.3.10",
    "@feathersjs/errors": "^4.3.10",
    "@feathersjs/express": "^4.3.10",
    "@feathersjs/feathers": "^4.3.10",
    "@feathersjs/socketio": "^4.3.10",
    "@feathersjs/transport-commons": "^4.3.10",
    "compression": "^1.7.4",
    "cors": "^2.8.5",
    "feathers-mongoose": "^8.1.0",
    "helmet": "^3.21.2",
    "mongodb-core": "^3.2.7",
    "mongoose": "^5.7.8",
    "serve-favicon": "^2.5.0",
    "winston": "^3.2.1"
  },
  "devDependencies": {
    "axios": "^0.19.0",
    "eslint": "^6.6.0",
    "mocha": "^6.2.2",
    "nodemon": "^1.19.4"
  }

NodeJS version:v12.13.0 Operating System:Linux 18.04

hammooooody avatar Nov 10 '19 11:11 hammooooody

Did you register the system strategy as another localStrategy instance?

authService.register('system', new LocalStrategy());

daffl avatar Nov 10 '19 23:11 daffl

Thank you @daffl. My system client (which is basically a nodejs application) is now able to login with credentials local strategy. But failing to authenticate with jwt token

server authentication.js

const { AuthenticationService, JWTStrategy } = require("@feathersjs/authentication");
const { LocalStrategy } = require("@feathersjs/authentication-local");
const { expressOauth } = require("@feathersjs/authentication-oauth");

module.exports = app => {
  const authentication = new AuthenticationService(app, "authentication");

  authentication.register("jwt", new JWTStrategy());
  authentication.register("local", new LocalStrategy());
  authentication.register("system", new LocalStrategy());

  app.use("/authentication", authentication);

  app.configure(expressOauth());
};

client authenticate jwt

app
      .authenticate({
        strategy: "jwt",
        accessToken
      })
      .then(res => {
        console.log("System Logged in via token!");
      })
      .catch(err => {
        console.log("JWT token is Invalid!", err);
      });

producing the error below:

 { Error: An id must be provided to the 'get' method
       at Object.validate (/<path>/node_modules/@feathersjs/feathers/lib/hooks/base.js:23:11)
       at /<path>/node_modules/@feathersjs/commons/lib/hooks.js:115:46
       at processTicksAndRejections (internal/process/task_queues.js:93:5)
     hook:
      { type: 'before',
        arguments: [Array],
        service: [Object],
        app: [Object],
        method: 'create',
        path: 'authentication',
        data: [Object],
        params: {} } } }

Excuse my newbie terminology here, I do realize that I need to somehow direct/reference jwt authentication to a user or a system, which is something I need to study.

I am not sure how to do that yet!

hammooooody avatar Nov 13 '19 04:11 hammooooody

Suggesting an alternative approach

Do you reckon it is better (easier) to separate the authentication into two separate services as I did below: default.json

"authentication": {
    "entity": "user",
    "service": "users",
    "secret": "<secret>",
    "authStrategies": ["jwt", "local"],
    "jwtOptions": {
      "header": {
        "typ": "access"
      },
      "audience": "https://yourdomain.com",
      "issuer": "feathers",
      "algorithm": "HS256",
      "expiresIn": "1d"
    },
    "local": {
      "usernameField": "email",
      "passwordField": "password"
    }
  },
  "systems-authentication": {
    "entity": "system",
    "service": "systems",
    "secret": "<secret>",
    "authStrategies": ["jwt", "local"],
    "jwtOptions": {
      "header": {
        "typ": "access"
      },
      "audience": "https://yourdomain.com",
      "issuer": "feathers",
      "algorithm": "HS256",
      "expiresIn": "1d"
    },
    "local": {
      "usernameField": "sysUsername",
      "passwordField": "sysPassword"
    }
  },

Then registered them as follows authentication.js

module.exports = app => {
  const authentication = new AuthenticationService(app, "authentication");
  const systemsAuthentication = new AuthenticationService(app, "systems-authentication");

  authentication.register("jwt", new JWTStrategy());
  authentication.register("local", new LocalStrategy());
  systemsAuthentication.register("jwt", new JWTStrategy());
  systemsAuthentication.register("local", new LocalStrategy());

  app.use("/authentication", authentication);
  app.use("/systems-authentication", systemsAuthentication);

  app.configure(expressOauth());
};

I also had to change my systems.hooks.js from

      authenticate("jwt")

to

      authenticate({
        service: "systems-authentication",
        strategies: ["jwt"]
      })

on client side I did the setup with custom authentication path systems-authentication

const feathers = require("@feathersjs/feathers");
const socketio = require("@feathersjs/socketio-client");
const io = require("socket.io-client");
const authentication = require("@feathersjs/authentication-client");

// this is the server url
const socket = io("<server-url>", { transports: ["websocket"], forceNew: true });

const cloudClient = feathers();

cloudClient.configure(socketio(socket));
cloudClient.configure(authentication({ path: "/systems-authentication" }));  // < --- Here

With that I managed to successfully authenticate 'system' via both local and jwt strategies without issues.

Do you agree with this approach or am I over complicating things here?

hammooooody avatar Nov 13 '19 05:11 hammooooody

Suggesting an alternative approach

Do you reckon it is better (easier) to separate the authentication into two separate services as I did below: default.json

"authentication": {
    "entity": "user",
    "service": "users",
    "secret": "<secret>",
    "authStrategies": ["jwt", "local"],
    "jwtOptions": {
      "header": {
        "typ": "access"
      },
      "audience": "https://yourdomain.com",
      "issuer": "feathers",
      "algorithm": "HS256",
      "expiresIn": "1d"
    },
    "local": {
      "usernameField": "email",
      "passwordField": "password"
    }
  },
  "systems-authentication": {
    "entity": "system",
    "service": "systems",
    "secret": "<secret>",
    "authStrategies": ["jwt", "local"],
    "jwtOptions": {
      "header": {
        "typ": "access"
      },
      "audience": "https://yourdomain.com",
      "issuer": "feathers",
      "algorithm": "HS256",
      "expiresIn": "1d"
    },
    "local": {
      "usernameField": "sysUsername",
      "passwordField": "sysPassword"
    }
  },

Then registered them as follows authentication.js

module.exports = app => {
  const authentication = new AuthenticationService(app, "authentication");
  const systemsAuthentication = new AuthenticationService(app, "systems-authentication");

  authentication.register("jwt", new JWTStrategy());
  authentication.register("local", new LocalStrategy());
  systemsAuthentication.register("jwt", new JWTStrategy());
  systemsAuthentication.register("local", new LocalStrategy());

  app.use("/authentication", authentication);
  app.use("/systems-authentication", systemsAuthentication);

  app.configure(expressOauth());
};

I also had to change my systems.hooks.js from

      authenticate("jwt")

to

      authenticate({
        service: "systems-authentication",
        strategies: ["jwt"]
      })

on client side I did the setup with custom authentication path systems-authentication

const feathers = require("@feathersjs/feathers");
const socketio = require("@feathersjs/socketio-client");
const io = require("socket.io-client");
const authentication = require("@feathersjs/authentication-client");

// this is the server url
const socket = io("<server-url>", { transports: ["websocket"], forceNew: true });

const cloudClient = feathers();

cloudClient.configure(socketio(socket));
cloudClient.configure(authentication({ path: "/systems-authentication" }));  // < --- Here

With that I managed to successfully authenticate 'system' via both local and jwt strategies without issues.

Do you agree with this approach or am I over complicating things here?

How about if you want the systemAuth and the normal Auth to use a common service

For instance a normal auth user wants to use a message service and likewise the systemAuth user.

knowsarzehmeh avatar Mar 13 '20 10:03 knowsarzehmeh

You may create a custom hook to check which entity is being used for auth:

// Use this hook to manipulate incoming or outgoing data.
// For more information on hooks see: http://docs.feathersjs.com/api/hooks.html
import { Hook, HookContext } from '@feathersjs/feathers';
import { iff } from 'feathers-hooks-common';
import * as authentication from '@feathersjs/authentication';

const { authenticate } = authentication.hooks;

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default (options = {}): Hook => {
  return iff(
    (context: HookContext): boolean => !!context.params.%YOUR-CUSTOM-AUTH-ENTITY%,
    authenticate({
      service: '%YOUR-CUSTOM-AUTH-SERVICE%-authentication',
      strategies: ['jwt']
    }),
    authenticate('jwt')
  );
};

and in service(s):

import multiAuthenticate from '../../hooks/multi-authenticate';
// Don't remove this comment. It's needed to format import lines nicely.

export default {
  before: {
    all: [
      multiAuthenticate() // instead of authenticate('jwt')
    ],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  },

  after: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  },

  error: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  }
};

Alex-Craftsman avatar Jul 23 '20 08:07 Alex-Craftsman