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

Problem authenticating using REST middleware #495

Closed
kokujin opened this issue May 4, 2017 · 15 comments
Closed

Problem authenticating using REST middleware #495

kokujin opened this issue May 4, 2017 · 15 comments

Comments

@kokujin
Copy link

kokujin commented May 4, 2017

I am trying to authenticate and redirect using REST and not any of the clients using this snippet

 app.post('/login', auth.express.authenticate('local', {
        successRedirect: '/dashboard',
        failureRedirect: '/login'
    }));

This fails however with an error

TypeError: req.app.authenticate is not a function

What would be the proper way to do this? thanks

@kokujin kokujin changed the title Problem with auth without client Problem with authenticating using REST middleware May 4, 2017
@kokujin kokujin changed the title Problem with authenticating using REST middleware Problem authenticating using REST middleware May 4, 2017
@ekryski
Copy link
Member

ekryski commented May 10, 2017

@kokujin did you include feathers authentication?

app.configure(authentication(config));

You need to have done that before your route is called (and also potentially registered).

@kokujin
Copy link
Author

kokujin commented May 11, 2017

Actually, I did @ekryski , more details
routes.js

module.exports = function() {
    const app = this;

 app.post('/login', auth.express.authenticate('local', {
        successRedirect: '/dashboard',
        failureRedirect: '/login'
    }));

 app.get('/login', function(req, res) {
         var context = {};
        res.render('login', context);
    });
 }

And in app.js, I import the routes for use

....

app.configure(hooks());
app.configure(mongodb);
app.configure(rest());
app.configure(authentication);

// Load and configure routes
app.configure(routes);
......
````

@snewell92
Copy link

What if you switch app.post/app.get with app.use? That's what I use in my routes and it seems to work.
Also, it might be helpful to post a repo.

@kokujin
Copy link
Author

kokujin commented May 18, 2017

Any help with this would really be appreciated. Just to make things clear,

  • I can create users
  • I can access services protected by hooks using a token

What does not work properly is protecting custom routes with the auth.express.authenticate middleware. The routes are locked but I cannot access them using a valid token issued from the same server.

Thanks

@snewell92
Copy link

What @kokujin just described is what I am experiencing in #469 currently. In the next day or two I might have a minimal example that hopefully shows how to get the auth.express.authenticate middleware working in those routes. I'm getting 401 Forbidden's despite having good tokens in the header/cookie.

@snewell92
Copy link

snewell92 commented May 18, 2017

I used feathers generate and got to the same state. I can create users / validate them with Postman, but when I use /login route with normal html form, the redirect breaks.

Steps to repro what I have:

$ mkdir new-test-app
$ cd new-test-app
$ feathers generate app .
  (src | yarn | REST, SocketIO)
$ feathers generate connection
  (I choose sequelize -> mysql, probably doesn't matter)
$ feathers generate service
  (users | sequelize -> mysql...)
$ feathers generate authentication
$ yarn install
$ yarn add ejs

At this point do an npm start, via postman we should be able to create users and authenticate them. Yay!

Now if we add routes like so...
routes.js

const feathers = require('feathers');
const auth = require('feathers-authentication');

module.exports = function() {
    const app = this;

    app.set('view engine', 'ejs');

    app.post(
        '/login',
        auth.express.authenticate('local', {
            successRedirect: '/home',
            failureRedirect: '/fail'
        })
    );

    app.use(
        '/home',
        auth.express.authenticate('jwt'),
        (req, res) => {
            res.render('home');
        }
    );

    app.use(
        '/fail',
        (req, res) => {
            res.render('fail');
        }
    );

    return app;
}

With appropriate pages in the views folder (fail.ejs and home.ejs) and a basic login form on index.html...

<form method="POST" action"/login">
  <label>Username</label>
  <input type="text" name="username"/>
  <label>Password</label>
  <input type="password" name="password"/>
  <input type="submit"/>
</form>

We can now navigate to /fail, but not /home - but here's the problem.
When we go to / and then put in email(or username with \\username in default.config)/password and then press enter, I'm getting a 404. What?

This happens for both 1.2.0 and 1.2.3 of feathers-authentication


Round two - what if we need to enable cookies for this to work? We shouldn't because the server should set the header and pass it to the redirect route, but if that solves it I'm down... trying that next... -> 'cookie' property in the authentication config doesn't make a difference. Nor does session.

@kokujin
Copy link
Author

kokujin commented May 24, 2017

Any news on this?

@snewell92
Copy link

@kokujin Are you still getting the same error?

TypeError: req.app.authenticate is not a function

Because I don't get that error. My users just don't get authenticated.

This is what my authentication.js files looks like:

const authentication = require('feathers-authentication');
const jwt = require('feathers-authentication-jwt');
const local = require('feathers-authentication-local');

module.exports = function() {
  const app = this;
  const config = app.get('authentication');

  // Set up authentication with the secret
  app.configure(authentication(config));
  app.configure(local(config.local));
  app.configure(jwt());

  // The `authentication` service is used to create a JWT.
  // The before `create` hook registers strategies that can be used
  // to create a new valid JWT (e.g. local or oauth2)
  app.service('authentication').hooks({
    before: {
      create: [
        authentication.hooks.authenticate(config.strategies)
      ],
      remove: [
        authentication.hooks.authenticate('jwt')
      ]
    }
  });
};

And in default.json I have an authentication property with proper fields set (they were all set up with just using feathers generate authentication).

@kokujin
Copy link
Author

kokujin commented May 27, 2017

Same here @snewell92, the

TypeError: req.app.authenticate is not a function

is gone, but no authentication is taking place.

@snewell92
Copy link

snewell92 commented May 30, 2017

@kokujin Okay I've got this working but I've added sessions (express-session) to explicitly manage that state. I think feathers is moving away or already has moved away from session based authorization, but that's essentially what we're wanting.

I require in express-session as session, then in my app.js file, after the bodyParser middleware (before rest/socket/hooks/auth) I have this line:

app
  .use(session({
    secret: app.get('session').secret,
    resave: false,
    saveUninitialized: true,
    cookie: { secure: isProd } // use secure cookies on production.
  }));

And then my post login route has this function

(req, res) => {
      app.service('authentication')
        .create({ username: req.body.username, password: req.body.password })
        .then((data) => {
          req.session.token = data.accessToken;
          res.redirect('/admin-home');
        })
        .catch(() => {
          res.redirect('/fail');
        });
    }

And then to enforce this authentication on routes I use two middlewares let authRoute = [sessionAuth, auth.express.authenticate('jwt')]; - sessionAuth is a custom made one that pulls the JWT out of the session. This is what that looks like:

// Checks if user has been authenticated
const logger = require('winston');

module.exports = (req, res, next) => {
  if (req.session.token) {
    let authVal = `Bearer ${req.session.token}`;
    req.headers['authorization'] = authVal;
    next();
  } else {
    logger.info('No token, redirecting...');
    // TODO set something in response header...?
    res.redirect('/');
  }
};

As it stands, this is a decent workaround that gets the desired behavior without too much hassle, and even works with existing middleware for authentication. The documentation for feathers seems to suggest there is a configuration to set up sessions, but maybe that has been deprecated(?) - either way I think this issue can be closed. If there is indeed an issue with the documentation another issue should address that.

OR it's also possible that both of our configs were just wrong. 🤷‍♂️
@ekryski What do you think? Did we just do something wrong (then the issue can be closed I guess)?

@kokujin
Copy link
Author

kokujin commented May 30, 2017

@snewell92, thanks for your persistence to solve this issue.

I would rather not use sessions and cookies, we have the JWT for that. I hope somebody has a good solution to this problem.

@snewell92
Copy link

@kokujin check this out. Seemed to work for him. It's probably a slightly different use case than ours, but it seems to pull the jwt token out from the cookie without needing to manage session ourselves. I'll probably go back and try req.headers['authorization'] = res.cookies['feathers-jwt'] to see if I can get rid of my explicitly managed session.

@snewell92
Copy link

Hey @kokujin are you still having this issue? I've managed to get a token back, put it in localStorage, and I have been having success with the auth.express.setCookie middleware that feathers-authentication comes with. Except today - I'm thinking I need to recreate some configs. I've moved away from express middleware though, and am using the feathers client on my login form page.

@ekryski
Copy link
Member

ekryski commented Jul 24, 2017

Hey sorry guys.

I've moved away from express middleware though, and am using the feathers client on my login form page.

Yeah we usually use the feathers client on the login page. As part of Auth 2.0, we're going to move to defaulting to putting the access token in the URL when you are redirecting instead of a cookie. Lots of people having trouble/confusion with cookies.

In order to do any sort of redirecting after you authenticate you currently need to use cookies to pass the JWT access token to the client. If you are using the feathers client in the app you are redirecting to (ie. redirecting to a client side app on a sub-domain), you can simply call client.authenticate() and your client should pick up the JWT access token from the cookie and then use it to make calls against your back-end server like normal (ie. via an Authorization header).


If you are not using JavaScript to talk to your backend via REST or Websockets, then you are likely doing some sort of SSR setup or just regular form posts. In order to handle that you need to use cookies server side and you need to add a custom cookie extractor to the feathers-authentication-jwt plugin. I wrote about that here: #389 (comment). The way we use cookies here is exactly the same way that sessions would work. The only difference is you don't need a session store because the JWT is parsed from the cookie on each request.

We generally discourage this method because we feel that you should be decoupling your API from your view layer and using cookies exposes you to potential CSRF vulnerabilities. So if you need to do this method you want to make sure you are setting your cookies to httpOnly: true and secure: true and using HTTPS. See the docs on how to set those options.


In the next major auth version we're going to be setting the token in the URL when doing redirects by default. This should alleviate the need for cookies for most people and will hopefully make all this much easier for people as there will be less moving parts.

@daffl
Copy link
Member

daffl commented Jan 22, 2018

@feathersjs/authentication-jwt has been updated to also parse cookies if they are set, basically incorporating @snewell92's suggestion.

There is a recipe showing how to use Feathers authentication with Express middleware (including server side rendering) at https://docs.feathersjs.com/guides/auth/recipe.express-middleware.html

@daffl daffl closed this as completed Jan 22, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants