feathersjs / feathers

The API and real-time application framework
https://feathersjs.com
MIT License
15.04k stars 751 forks source link

Cannot modify result in after hook #617

Closed AlexanderArvidsson closed 7 years ago

AlexanderArvidsson commented 7 years ago

First of all, I am new to feathers and I may be doing something wrong, but as far as my research tells me, it should be fine.

I am trying to assign a JWT token to registered users, but when I check the database afterwards the token is not there. I'm using feathers-rethinkdb at the moment, but using feathers-memory yields the same result.

const issueJWT = () => {
  return hook => {
    const app = hook.app;
    const id = hook.result.id;

    return app.passport.createJWT({ userId: id }, app.get('authentication')).then(accessToken => {
      hook.result.accessToken = accessToken;

      debug(`Created access token ${accessToken}`);

      return hook;
    });
  };
};

module.exports = {
  after: {
    create: [
      issueJWT(),
    ],
  },
};

Also, when using RethinkDB, the after hook runs twice (Probably the same issue as feathersjs/feathers-rethinkdb#80), but this is unrelated to my current issue.

daffl commented 7 years ago

After hooks run after the database call happened and let you modify what is returned to the client not what gets put into the database. If you want to change the data that are being saved you have to register a before create hook and modify hook.data. For more examples on this, have a look at the hooks API page.

AlexanderArvidsson commented 7 years ago

Hm, okay, seems like I misunderstood something from the examples I've looked at. Should have thought about that, though. One question though; if the JWT access token is not stored in the database, how does feathers authenticate against it? Also, how do I store the access token on the client in localStorage or something similar? I am using passport-steam to login using steam credentials which doesn't give me a JWT access token, so I assumed I had to generate my own.

This is what I've set up with passport right now. What I don't understand is how the access token gets stored, both on the client and on the server. Once I sign in, the user is created / updated, but there's never any access token in cookies or localStorage, and not in the server database either. So, my question is, how do I authenticate after I have signed in through steam?

app.passport.use(
  new SteamStrategy({
    returnURL: 'http://localhost:3030/auth/steam/return',
    realm: 'http://localhost:3030/',
    apiKey: '<apikey>'
  },
  function(identifier, profile, done) {
    const query = { steamId: profile.id };

    const data = { identifier, profile };

    app.service('users').find({ query })
      .then(user => user.total > 0 ? updateUser(user.data[0], data) : createUser(data))
      .then(user => {
        done(null, user);
      });
  }));

app.get('/auth/steam', authentication.express.authenticate('steam'));

app.get('/auth/steam/return',
  authentication.express.authenticate('steam', { failureRedirect: '/login' }),
  function(req, res) {
    // Successful authentication, redirect home.
    res.redirect('/');
  });
marshallswain commented 7 years ago

One question though; if the JWT access token is not stored in the database, how does feathers authenticate against it?

The JWT contains a payload, a signature, and an expiration. Check out https://jwt.io to see the internals color coded. It's really helpful.

daffl commented 7 years ago

I also think you should look into feathers-authentication oauth2 (with the passport-steam strategy).

Using it together with Feathers on the client all the JWT issuing, consuming and storing int localstorage will be done for you automatically already.

AlexanderArvidsson commented 7 years ago

I assumed passport-steam didn't work with the OAuth2 package because it's OpenID rather than OAuth2? Are you saying it works fine with it?

marshallswain commented 7 years ago

I'm pretty sure it works.

AlexanderArvidsson commented 7 years ago

Alright, I've hooked up the steam strategy to the oauth2 service. First of all, the package requires a clientID and a clientSecret, but passport-steam does not require any of those (only apiKey). After I sign in and get redirected back to returnURL, I am met with a RethinkDB query error:

r.db("feather_test").table("users").filter(function(var_3) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    return r.expr({}).and(var_3("steamId").eq(undefined))
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}).limit(1)
^^

error:  ReqlRuntimeError
    at Function.Term.run (E:\Development\FeathersJS\meta-drm\node_modules\rethinkdbdash\lib\term.js:43:17)
    at Function.Term.then (E:\Development\FeathersJS\meta-drm\node_modules\rethinkdbdash\lib\term.js:2876:15)
    at process._tickCallback (internal/process/next_tick.js:109:7)
error: Unhandled Rejection at: Promise  Promise {
  <rejected> TypeError: done is not a function
    at E:\Development\FeathersJS\meta-drm\node_modules\feathers-authentication-oauth2\lib\verifier.js:139:24 } TypeError: done is not a function
    at E:\Development\FeathersJS\meta-drm\node_modules\feathers-authentication-oauth2\lib\verifier.js:139:24

The service is set up like this

  app.configure(oauth2({
    name: 'steam',
    Strategy: SteamStrategy,
    clientID: 'not needed',
    clientSecret: 'not needed',
    apiKey: '<steam api key>',
    returnURL: 'http://localhost:3030/auth/steam/callback',
  }));

OpenID requires a returnURL, not callbackURL as the oauth2 service specifies by default.

Switching to feathers-memory rather than RethinkDB service gives me this error:

error: Unhandled Rejection at: Promise  Promise {
  <rejected> TypeError: done is not a function
    at E:\Development\FeathersJS\meta-drm\node_modules\feathers-authentication-oauth2\lib\verifier.js:139:24
    at process._tickCallback (internal/process/next_tick.js:109:7) } TypeError: done is not a function
    at E:\Development\FeathersJS\meta-drm\node_modules\feathers-authentication-oauth2\lib\verifier.js:139:24
    at process._tickCallback (internal/process/next_tick.js:109:7)

Apologize that this issue has kind of been everywhere by now, I'm simply trying to get into the whole feathers.js mindset.

marshallswain commented 7 years ago

Maybe this issue will help: https://github.com/feathersjs/feathers-authentication/issues/154

AlexanderArvidsson commented 7 years ago

That's the exact issue that I looked at to write the initial code, the one that didn't give me a JWT token and didn't use oauth2. Are you telling me to switch back, again?

marshallswain commented 7 years ago

Nope. I just remembered seeing it and I'm trying to help while I do other things. ;)

AlexanderArvidsson commented 7 years ago

The reason the oauth2 verifier fails with this is partly due to the SteamStrategy not returning OAuth2 tokens. See: https://github.com/liamcurry/passport-steam/blob/master/lib/passport-steam/strategy.js#L103 https://github.com/feathersjs/feathers-authentication-oauth2/blob/master/src/verifier.js#L74

OAuth2 expects the strategy to get the params req, accessToken, refreshToken, profile, done, while passport-steam passes req, identity, profile, done instead.

From what I can tell, using the oauth2 package won't work for me. As such, I need to do it manually like I did previously. So the question falls back to what it was previously: How do I get a JWT token from passport, and how do I store it (both on server and client).

daffl commented 7 years ago

You can probably solve this by customizing the verifier since it gets passed the same params as the callback.

AlexanderArvidsson commented 7 years ago

Oh yeah, I remember reading that page. I totally disregarded the fact that I could customize the verifier. Will try this, thanks!

AlexanderArvidsson commented 7 years ago

Alright, I have configured the oauth2 package with a custom verifier and the login is now successful. The users are updated/patched whenever you sign in. However, the client still tells me

{
  "name": "NotAuthenticated",
  "message": "Could not find stored JWT and no authentication strategy was given",
  "code": 401,
  "className": "not-authenticated",
  "errors": {}
}
app.configure(auth());

app.authenticate()
  .then(response => {
    console.info('Feathers Client has Authenticated with the JWT access token!');
    console.log(response);
  })
  .catch(error => {
    console.info('We have not logged in with OAuth, yet.  This means there\'s no cookie storing the accessToken.  As a result, feathersClient.authenticate() failed.');
    console.log(error);
  });
AlexanderArvidsson commented 7 years ago

Issue solved, I was not pointing my returnURL to the right location. Should be /auth/steam/callback rather than /auth/steam/return. Oops. Thanks for the help, though!

lock[bot] commented 5 years ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue with a link to this issue for related bugs.