feathersjs-ecosystem / authentication

[MOVED] Feathers local, token, and OAuth authentication over REST and Websockets using JSON Web Tokens (JWT) with PassportJS.
MIT License
317 stars 118 forks source link

JWT Authentication setup failing #411

Closed subodhpareek18 closed 7 years ago

subodhpareek18 commented 7 years ago

Here's my code running on windows 10.

import feathers from 'feathers';
import rest from 'feathers-rest';
import bodyParser from 'body-parser';
import auth from 'feathers-authentication';
import jwt from 'feathers-authentication-jwt';

const init = () => {
  const app = feathers();

  app.configure(rest());
  app.use(bodyParser.json());
  app.use(bodyParser.urlencoded({ extended: true }));
  app.configure(auth({ secret: 'my-secret' }));
  app.configure(jwt());
  app.use('/messages', {
    get(id, params) {
      console.log(params);
      return Promise.resolve({
        id,
        read: false,
        text: 'Feathers is great!',
        createdAt: new Date()
          .getTime()
      });
    }
  });

  app.listen(3030);
};

export const run = () => init();

Have also tried this:

import feathers from 'feathers';
import rest from 'feathers-rest';
import bodyParser from 'body-parser';
import auth from 'feathers-authentication';

const init = () => {
  const app = feathers();

  app.configure(rest());
  app.use(bodyParser.json());
  app.use(bodyParser.urlencoded({ extended: true }));
  app.configure(auth({ token: { secret: 'my-secret' } }));
  app.use('/messages', {
    get(id, params) {
      console.log(params);
      return Promise.resolve({
        id,
        read: false,
        text: 'Feathers is great!',
        createdAt: new Date()
          .getTime()
      });
    }
  });

  app.listen(3030);
};

export const run = () => init();

Getting this error in both cases:

D:\code\playground\core-new\feathers\node_modules\feathers-authentication\lib\services\token.js:25
    tokenService.before({
                 ^

TypeError: tokenService.before is not a function
    at EventEmitter.<anonymous> (D:\code\playground\core-new\feathers\node_modules\feathers-authentication\lib\services\token.js:25:18)
marshallswain commented 7 years ago

@zusamann, please post the version numbers of each of the imported modules.

subodhpareek18 commented 7 years ago

Latest ones on NPM I think, since I just did a npm i -S ......

    "feathers": "^2.0.3",
    "feathers-authentication": "^0.7.11",
    "feathers-authentication-jwt": "^0.3.1"

@marshallswain

daffl commented 7 years ago

If you are using feathers-authentication-<strategy> you need to explicitly install feathers-authentication@^1.0.0.

subodhpareek18 commented 7 years ago

But as you can see above @daffl I did two attempts, with and without feathers-authentication-<strategy> got the same error in both cases.

subodhpareek18 commented 7 years ago

Ok here I am using "feathers-authentication": "^1.0.2" and getting a slightly different error.

import feathers from 'feathers';
import rest from 'feathers-rest';
import bodyParser from 'body-parser';
import auth from 'feathers-authentication';
import jwt from 'feathers-authentication-jwt';
import sequelize from 'feathers-sequelize';
const db = require('./models');

const init = () => {
  const app = feathers();

  app.configure(rest());
  app.use(bodyParser.json());
  app.use(bodyParser.urlencoded({ extended: true }));
  app.configure(auth({ secret: 'my-secret' }));
  app.configure(jwt());

  Object.keys(db.sequelize.models)
    .map(key =>
      app.use(`/rest/${key}`, sequelize({
        Model: db[key],
        paginate: {
          default: 5,
          max: 25
        }
      }))
    );

  app.listen(3030);
};

export const run = () => init();
D:\code\playground\core-playground\feathers\node_modules\feathers-authentication-jwt\lib\verifier.js:32
      throw new Error('options.service does not exist.\n\tMake sure you are passing a valid service path or service instance and it is initialized before feathers-authentication-jwt.');
      ^

Error: options.service does not exist.
        Make sure you are passing a valid service path or service instance and it is initialized before feathers-authentication-jwt.
marshallswain commented 7 years ago

@zusamann, check out the default options for the jwt plugin here: https://github.com/feathersjs/feathers-authentication-jwt#default-options. You probably need to fix the service attribute (Or create a user service if you haven't, yet.)

subodhpareek18 commented 7 years ago

Yes you are right @marshallswain this error is not thrown anymore. I had to add the following:

import memory from 'feathers-memory';
// ...
// ...
app.use('/users', memory());

Now I was looking to finally use the authentication on all of my endpoints which I'm generating procedurally like so:

  Object.keys(db.sequelize.models).map(key =>app.use(`/rest/${key}`, sequelize({ Model: db[key] })));

So I added the following:

  app.service('authentication').hooks({
    before: {
      all: [
        auth.hooks.authenticate('jwt')
      ]
    }
  });

But it is generating the following error:

D:\code\playground\core-playground\feathers\server.js:64
  app.service('authentication').hooks({
                                ^

TypeError: app.service(...).hooks is not a function
    at init (D:/code/playground/core-playground/feathers/server.js:32:33)
marshallswain commented 7 years ago

@zusamann you probably just need to install the hooks plugin: https://github.com/feathersjs/feathers-hooks#quick-start

subodhpareek18 commented 7 years ago

But not import it anywhere?

marshallswain commented 7 years ago

Use the quick start I just linked.

subodhpareek18 commented 7 years ago

Right again @marshallswain, had to add the following to the code:

import hooks from 'feathers-hooks';
// ...
// ...
app.configure(hooks());

And modify the service creation so the hooks are added over the keys.map.

  Object.keys(db.sequelize.models)
    .map(key =>
      app.use(`/rest/${key}`, sequelize({
        Model: db[key],
        paginate: {
          default: 5,
          max: 25
        }
      }))
      .hooks({
        before: {
          all: [
            auth.hooks.authenticate(['jwt'])
          ]
        }
      })
    );

Thank you so much for your help.

I was wondering if the docs could be updated to reflect some of the nuances covered above. Of placing a users service and configuring hooks, etc.

marshallswain commented 7 years ago

The Auk docs will contain all of the information for feathers-authentication@1.0.x. Just out of curiosity, why did you decide to install feathers-authentication-jwt? How did you discover it? I ask because the current release of feathers-authentication on npm already includes that functionality. We decided to implement auth plugins in the upcoming release (which you've just upgraded to using)

subodhpareek18 commented 7 years ago

I have actually just recently discovered feathers, like 1-2 days ago and have just been reading all the repos, documentation, changelog, issues I could find. Learned about auk release there itself.

I just started doing some PoCs today, hence hitting all these confusions and errors.

marshallswain commented 7 years ago

I see. We have big docs updates underway as the final part of the Auk release, so the confusion will disappear, shortly.

subodhpareek18 commented 7 years ago

Glad to hear that, do you think the already out releases of Auk like the new authentication plugin are stable enough to use in production? Cause I'm really looking forward to using all of what I can find.

marshallswain commented 7 years ago

feathers-permissions will see some changes. Everything else is stable.

subodhpareek18 commented 7 years ago

Great! Looking forward to it.

marshallswain commented 7 years ago

In the meantime, using feathers-authentication@1.0.x, you'll probably want to familiarize yourself with the old authentication hooks. Some might work, but others you might have to modify. https://github.com/feathersjs/feathers-legacy-authentication-hooks

subodhpareek18 commented 7 years ago

Back again, having trouble setting up the client side usage.

/** 
    server.js
    "feathers": "^2.0.3",
    "feathers-authentication": "^1.0.2",
    "feathers-authentication-jwt": "^0.3.1",
**/

import feathers from 'feathers';
import rest from 'feathers-rest';
import bodyParser from 'body-parser';
import auth from 'feathers-authentication';
import jwt from 'feathers-authentication-jwt';
import hooks from 'feathers-hooks';
import errorHandler from 'feathers-errors/handler';
import memory from 'feathers-memory';
import sequelize from 'feathers-sequelize';
import cors from 'cors';
import socketio from 'feathers-socketio';
const db = require('./models');

const init = () => {
  const app = feathers();

  app.use(cors());
  app.configure(socketio({
    wsEngine: 'uws'
  }));
  app.configure(rest());
  app.use(bodyParser.json());
  app.use(bodyParser.urlencoded({ extended: true }));
  app.configure(hooks());
  app.configure(auth({ secret: 'secret' }));
  app.configure(jwt());
  app.use('/users', memory());

  Object.keys(db.sequelize.models)
    .map(key => {
      app.use(`/db/${key}`, sequelize({
        Model: db[key],
        paginate: {
          default: 10,
          max: 25
        }
      }));
      app.service(`/db/${key}`).hooks({
        before: {
          all: [
            auth.hooks.authenticate(['jwt'])
          ]
        }
      });
    });

  app.use(errorHandler());
  app.listen(3030);
};

export const run = () => init();
/**
    client.js
    "feathers-client": "^1.9.0",
**/

import feathers from 'feathers-client';
// import io from 'socket.io-client';  
import axios from 'axios';

const host = 'http://localhost:3030';
const client = feathers()
  // .configure(feathers.socketio(io(host)))
  .configure(feathers.rest(host).axios(axios)) // tried both clients, explained below
  .configure(feathers.hooks())
  .configure(feathers.authentication({
    storage: window.localStorage
  }));

export default client;
/** 
    App.js (client side)
**/

class App extends Component {
  constructor() {
    super()
    this.state = { films: [] }
    client.authenticate({
      type: 'token',
      endpoint: '/authentication'  // without this the default post is to /auth/token, a bit confusing
    })
    .then(res => {
      console.log(res) // get a token with rest client, nothing comes with io client
      window.localStorage
        .setItem('feathers-jwt', res.accessToken); // tried without this first, that didnt work either
      client.service('/db/film').get(1).then(console.log) // errors out, 401 unautho...
    })
    .catch(rej => console.log(rej))
//...

So having trouble setting the fetched token in rest client, and with io client not getting any token at all. Both resulting in 401s. My thinking is it might be due to using incompatible server and client side auth libs.

subodhpareek18 commented 7 years ago

On further exploration, this works on with the rest client, but doesn't with the io client. Don't even get an error. Which tells me there needs to be done something entirely different with the io client.

/** 
    App.js (client side)
**/

class App extends Component {
  constructor() {
    super()
    this.state = { films: [] }
    client.authenticate({
      type: 'token',
      endpoint: '/authentication'
    })
    .then(res => {
      client.set('token', res.accessToken)
      client.service('/db/film').get(1).then(console.log)
    })
    .catch(rej => console.log(rej))
//...
marshallswain commented 7 years ago

@zusamann Looks like you're still using the old version of the auth client. You can upgrade by importing the new client plugin:

import auth from 'feathers-authentication-client'

...

.configure(auth({
    storage: window.localStorage
  }));
subodhpareek18 commented 7 years ago

I'm actually not using the individual clients. Rather "feathers-client": "^1.9.0", you suppose it's better to use broken up clients?

marshallswain commented 7 years ago

Nah. You can keep everything else the same. Just change the auth plugin part.

subodhpareek18 commented 7 years ago

Ok will try that and revert

subodhpareek18 commented 7 years ago
/**
    "feathers-authentication-client": "^0.1.7",
    "feathers-client": "^1.9.0",
**/

// client.js
import feathers from 'feathers-client';
import auth from 'feathers-authentication-client';
// import io from 'socket.io-client';
import axios from 'axios';

const host = 'http://localhost:3030';
const client = feathers()
  // .configure(feathers.socketio(io(host)))
  .configure(feathers.rest(host).axios(axios))
  .configure(feathers.hooks())
  .configure(auth({
    storage: window.localStorage
  }));

export default client;

// App.js
class App extends Component {
  constructor() {
    super()
    client.authenticate({
      type: 'token',
      endpoint: '/authentication'
    })
    .then(console.log)
    .catch(console.error)
  }
  render() {
// ...

Getting this error in my browser console twice.

base64_url_decode.js:14Uncaught (in promise) TypeError: Cannot read property 'replace' of undefined
    at module.exports (base64_url_decode.js:14)
    at module.exports (index.js:12)
    at passport.js:266

@marshallswain

marshallswain commented 7 years ago

The API has changed. You now need to specify the strategy, which refers to the name of the PassportJS strategy being used on the server. In the case of feathers-authentication-jwt, the name is just jwt. You can see, from looking at the expected params in the README.md: https://github.com/feathersjs/feathers-authentication-jwt#expected-request-data, that you also need to provide an accessToken property, which is the actual JWT.

client.authenticate({
  type: 'jwt',
  accessToken: window.localStorage.getItem('feathers-jwt')
})

A shortcut to the above would be to simply call client.authenticate(), with no params. The above and below are functionally equivalent.

client.authenticate()

If there is a token in storage, it will attempt to login. If not, it will return an error that no token was found.

subodhpareek18 commented 7 years ago

Actually I tried with all the various combinations of params. And just now I tried the below again.

    client.authenticate()
    .then(console.log)
    .catch(console.error)

Tried setting the token manually too

    client.authenticate()
    .then(res => window.localStorage.setItem('feathers-jwt', res.accessToken))
    .catch(console.error)

Getting the same error as above on both.

subodhpareek18 commented 7 years ago

It's actually erroring out even before the .authenticate() is being made. I commented out all code in my App.js except for import client from './client'; and getting an error there itself.

So there must be some thing wrong with this:

/**
    "feathers-authentication-client": "^0.1.7",
    "feathers-client": "^1.9.0",
**/

// client.js
import feathers from 'feathers-client';
import auth from 'feathers-authentication-client';
// import io from 'socket.io-client';
import axios from 'axios';

const host = 'http://localhost:3030';
const client = feathers()
  // .configure(feathers.socketio(io(host)))
  .configure(feathers.rest(host).axios(axios))
  .configure(feathers.hooks())
  .configure(auth({
    storage: window.localStorage
  }));

export default client;
marshallswain commented 7 years ago

Hmmm... Try switching to the individual modules.

subodhpareek18 commented 7 years ago
/**
    "feathers": "^2.0.3",
    "feathers-authentication-client": "^0.1.7",
    "feathers-hooks": "^1.7.1",
    "feathers-rest": "^1.6.0",
**/

import feathers from 'feathers/client';
import hooks from 'feathers-hooks';
import rest from 'feathers-rest/client';
import auth from 'feathers-authentication-client';
import axios from 'axios';

const host = 'http://localhost:3030';
const client = feathers()
  .configure(rest(host).axios(axios))
  .configure(hooks())
  .configure(auth({
    storage: window.localStorage
  }));

export default client;

Same error sadly.

marshallswain commented 7 years ago
base64_url_decode.js:14Uncaught (in promise) TypeError: Cannot read property 'replace' of undefined
    at module.exports (base64_url_decode.js:14)
    at module.exports (index.js:12)
    at passport.js:266

There's something else going on here. What's the path to passport.js in your project or node_modules? There is no reference to the replace function anywhere in the passport.js that's included in feathers-authentication-client. Which version do you have installed?

marshallswain commented 7 years ago

Oh you posted that above. Just need the path to that file, then.

marshallswain commented 7 years ago

I thought it was this file: https://github.com/feathersjs/feathers-authentication-client/blob/master/src/passport.js

But it's not in that file, directly. Can you trace it down?

subodhpareek18 commented 7 years ago

Looks like it's the dep jwt-decode // ./~/jwt-decode/lib/base64_url_decode.js

image

marshallswain commented 7 years ago

Can you post your code in a repo that I can pull?

subodhpareek18 commented 7 years ago

Yes sure, give me few minutes

subodhpareek18 commented 7 years ago

@marshallswain here is the code: https://github.com/zusamann/feathers-poc

There are two folders client-side and server-side you need to go to both and do an npm i & npm start. As for the database I'm using postgres with the sample db I found here http://www.postgresqltutorial.com/load-postgresql-sample-database/

In order to connect with your own db you'll have to change line 15 in server-side/models/index.js

let sequelize = new Sequelize('postgres://postgres:password@localhost:5432/dvdrental', databaseOptions);

You can alternatively load some completely different db and models as you see fit.

subodhpareek18 commented 7 years ago

Were you able to have a go? In any case what would be your suggestion @marshallswain @daffl , should I downgrade to a pre 1.0 feathers-authentication on my server and try there?

marshallswain commented 7 years ago

Trying this out right now.

marshallswain commented 7 years ago

@zusamann I made a PR to your repo. https://github.com/zusamann/feathers-poc/pull/1

Both local and jwt auth should work, now. You'll need to integrate the client, but it should be trivial, now.

I didn't import the Postgres database, and just focused on getting auth working.

subodhpareek18 commented 7 years ago

@marshallswain sorry but I'm getting the same error on the client side. Can you have a look at the client side code? It's in the client-side folder.

base64_url_decode.js:14Uncaught (in promise) TypeError: Cannot read property 'replace' of undefined
    at module.exports (base64_url_decode.js:14)
    at module.exports (index.js:12)
    at passport.js:266

Also didn't really understand the change you made to thee server side code. I mean apart from including the local auth strategy, it seems like you replaced memory with nedb but not much else for jwt auth. The auth was working fine on the server side earlier as well which is why I had closed the issue earlier. But at that point I was simply using POSTMAN, this time I was looking to use the client.

Also this change in server.js actually stopped my server from running as the file bootstrap.js expects a function run to work, so not sure how you were running the server. That's a minor point though.

-- export const run = () => init();
++ module.exports = () => init();
marshallswain commented 7 years ago

@zusamann

Can you have a look at the client side code? It's in the client-side folder.

I'll take a look later today.

Also didn't really understand the change you made to thee server side code.

The most important part was registering the hook on the /authentication service. None of the auth plugins will work without that.

marshallswain commented 7 years ago

@zusamann I think you might have a problem with your environment. I don't get the error you pasted, above. I just submitted another PR where I got auth working. Try removing your node_modules folders on the client and reinstall. It probably shouldn't matter, but I installed modules using Yarn.

Here's the PR: https://github.com/zusamann/feathers-poc/pull/2

subodhpareek18 commented 7 years ago

The most important part was registering the hook on the /authentication service. None of the auth plugins will work without that.

@marshallswain actually I was using /authentication to generate the tokens and using them to call the /db/films end point which was authenticated. This was an intended behavior actually for my customer facing landing page where the freely generated token is then used to authenticate lots of calls to the backend.

marshallswain commented 7 years ago

As for the server change, I was just using node server to run the code after I switched back to CommonsJS.

subodhpareek18 commented 7 years ago

Try removing your node_modules folders on the client and reinstall. It probably shouldn't matter, but I installed modules using Yarn.

Ok trying, though to be honest I did a clean install in a separate folder just to be sure when I pushed this code on github.

As for the server change, I was just using node server to run the code after I switched back to CommonsJS

Okay

marshallswain commented 7 years ago

I was using /authentication to generate the tokens and using them to call the /db/films end point. This was an intended behavior actually for my customer facing landing page where the freely generated token is then used to authenticate lots of calls to the backend.

I see. Let me try this.

marshallswain commented 7 years ago

I just tried this and got no errors. I think we're back to this being an environment issue.

screen shot 2017-02-03 at 11 59 31 am

subodhpareek18 commented 7 years ago

I think you might have a problem with your environment. I don't get the error you pasted, above. I just submitted another PR where I got auth working. Try removing your node_modules folders on the client and reinstall. It probably shouldn't matter, but I installed modules using Yarn.

Just tried again with a rm -rf node_modules and a yarn install inside the client-side folder and tried again. Same error actually, coming from the jwt-decode lib. Did you use the actual code in the client-side folder?

Also just to be clear the server side implementation was working as intended. A call to the open /authentication endpoint gave me a token which successfully let me call the other authenticated endpoints /db/....

It's just the client-side code is giving troubles.

Do you think it could be a windows issue? Should I try to setup the entire thing in vagrant or something?

subodhpareek18 commented 7 years ago

I see you have pushed another PR, let me try with that once.