Skip to content
This repository was archived by the owner on Mar 22, 2022. It is now read-only.

JWT Authentication setup failing #411

Closed
subodhpareek18 opened this issue Jan 31, 2017 · 60 comments
Closed

JWT Authentication setup failing #411

subodhpareek18 opened this issue Jan 31, 2017 · 60 comments

Comments

@subodhpareek18
Copy link

subodhpareek18 commented Jan 31, 2017

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
Copy link
Member

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

@subodhpareek18
Copy link
Author

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
Copy link
Member

daffl commented Feb 1, 2017

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

@subodhpareek18
Copy link
Author

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
Copy link
Author

subodhpareek18 commented Feb 1, 2017

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
Copy link
Member

@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
Copy link
Author

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
Copy link
Member

marshallswain commented Feb 1, 2017

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

@subodhpareek18
Copy link
Author

But not import it anywhere?

@marshallswain
Copy link
Member

Use the quick start I just linked.

@subodhpareek18
Copy link
Author

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
Copy link
Member

The Auk docs will contain all of the information for [email protected]. 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
Copy link
Author

subodhpareek18 commented Feb 1, 2017

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
Copy link
Member

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

@subodhpareek18
Copy link
Author

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
Copy link
Member

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

@subodhpareek18
Copy link
Author

Great! Looking forward to it.

@marshallswain
Copy link
Member

In the meantime, using [email protected], 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
Copy link
Author

subodhpareek18 commented Feb 2, 2017

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 subodhpareek18 reopened this Feb 2, 2017
@subodhpareek18
Copy link
Author

subodhpareek18 commented Feb 2, 2017

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
Copy link
Member

@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
Copy link
Author

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
Copy link
Member

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

@subodhpareek18
Copy link
Author

Ok will try that and revert

@subodhpareek18
Copy link
Author

/**
    "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
Copy link
Member

marshallswain commented Feb 2, 2017

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
Copy link
Author

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
Copy link
Author

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
Copy link
Member

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
Copy link
Author

subodhpareek18 commented Feb 2, 2017

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

image

@marshallswain
Copy link
Member

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

@subodhpareek18
Copy link
Author

Yes sure, give me few minutes

@subodhpareek18
Copy link
Author

subodhpareek18 commented Feb 2, 2017

@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
Copy link
Author

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
Copy link
Member

Trying this out right now.

@marshallswain
Copy link
Member

marshallswain commented Feb 3, 2017

@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
Copy link
Author

subodhpareek18 commented Feb 3, 2017

@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
Copy link
Member

@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
Copy link
Member

marshallswain commented Feb 3, 2017

@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
Copy link
Author

subodhpareek18 commented Feb 3, 2017

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
Copy link
Member

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

@subodhpareek18
Copy link
Author

subodhpareek18 commented Feb 3, 2017

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
Copy link
Member

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
Copy link
Member

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
Copy link
Author

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
Copy link
Author

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

@marshallswain
Copy link
Member

Oh, maybe this IS a Windows issue. I literally just opened the client-side folder, installed deps and ran npm start. It booted up your client code and immediately authenticated.

@subodhpareek18
Copy link
Author

subodhpareek18 commented Feb 3, 2017

It could also be an issue with create-react-app framework because the optimized production build that one gets after running npm run build is actually not giving me that error 😐 (I just ran it out of curiosity)

I'll try and set this up without a fancy framework tomorrow and get back to you, because if this is a windows + feathers issue it'll be a problem, not so much if it's a windows + cra + webpack-dev-server issue. I will also try and set up this thing in a dev env on vagrant explore that further.

Thank you and my apologies for the trouble 😅
@marshallswain

@subodhpareek18
Copy link
Author

subodhpareek18 commented Feb 3, 2017

Okay last test result for tonight.

Simply doing a node client.js in the console without any browser works fine for the rest client (one more thing pointing towards it not being a feathers + windows issue) but not for the ws client. Will test some more in the real browser tomorrow.

const feathers = require('feathers/client');
const hooks = require('feathers-hooks');
const rest = require('feathers-rest/client');
const auth = require('feathers-authentication-client');
const socketio = require('feathers-socketio/client');
const io = require('socket.io-client');
const axios = require('axios');
const localStorage = require('localstorage-memory');

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

client.authenticate({
  strategy: 'jwt'
})
.then(res => {
  console.log(res);
  client.service('/db/film').get(1)
  .then(console.log)
  .catch(console.error)
})
.catch(console.error)

@subodhpareek18
Copy link
Author

subodhpareek18 commented Feb 4, 2017

Ok now I can fully confirm that the above client side error was a webpack-dev-server issue and not a feathers-authentication-client issue.

webpack/webpack-dev-server#381
webpack/webpack-dev-server#117

I turned off my adblock extension on chrome and the error went away.


But haven't been able to solve my problem completely.

The intended behavior is the /authentication endpoint can freely issue tokens which are then used to make authenticated calls to other endpoints.

Now the rest client works fine in the above flow but the io client returns a 401, though this only happens for jwt strategy and not local which is fine for both io and rest. Please pull the latest code from https://github.com/zusamann/feathers-poc and give it a go.

@marshallswain


client.authenticate({
  strategy: 'jwt'
})

// fine with 
.configure(rest(host).axios(axios))

// 401 with
.configure(socketio(io(host)))
client.authenticate({
  strategy: 'local',
  email: '[email protected]',
  password: 'feathers'
})

// fine with 
.configure(rest(host).axios(axios))

// fine with
.configure(socketio(io(host)))

@subodhpareek18
Copy link
Author

subodhpareek18 commented Feb 4, 2017

Also after this last issue is solved, I was hoping that I could push this entire thing as a documented demo somewhere? Both the client and server side code as a fully realized app. Would like to make more demos of other things like hooks, etc and incorporate them in.

@shirish87
Copy link

Slightly off-topic, but I was looking to use feathers-authentication-jwt and looked into migrating to the new modules and APIs. Just some feedback -- there are a lot of moving parts involved. After spending 3 hours wiring things up, I gave up and resorted to forking the legacy branch and making it accept configurable functions for passport/checkCredentials and JWT sign/verify. gist here

For context -- we have an external token management microservice that issues a JWT based access_token along with a refresh_token, and I was looking to reuse the client + server auth stack in feathersjs to quickly build a web frontend.

@subodhpareek18
Copy link
Author

subodhpareek18 commented Feb 5, 2017

Had a small discussion with @marshallswain and we concluded that the exact flow that I was trying to achieve was not really a supported strategy in the current auth ecosystem.

A hypothetical feathers-authentication-anonymous adapter would be something that solves this problem completely maybe could use this plugin for it https://github.com/jaredhanson/passport-anonymous. For now an anonymous auth only seems to work through the rest client and not the io client.

@marshallswain I think you could probably close this issue in favor of another one detailing out a feature request, would be happy to further the discussion there.

@subodhpareek18
Copy link
Author

subodhpareek18 commented Feb 5, 2017

@shirish87 I think such issues are bound to happen with a release that is not even out yet. But I think it should come out soon, with proper docs, etc.

@shirish87
Copy link

@zusamann Totally agree. I was alluding to the convenience of a single-module approach (legacy feathers-authentication) vs. the flexibility of wired-up individual modules.

Both approaches have their pros and cons, but the community eventually gravitates to find the right balance :)

@ekryski
Copy link
Member

ekryski commented Feb 20, 2017

@zusamann if you have an anonymous feature request feel free to open an issue. It actually should work pretty much out of the box. I'll be working on documenting anonymous user auth. Going to close this now as things seem to be fairly sorted for you and this is a pretty lengthy thread. 😄

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants