Closed muvhaus-sl closed 6 years ago
On a side note, I wanted to ask if the Authentication 1.0.2 should populate the Users' table with more than the {provider}Id column. Currently I can only see the that column getting populated while the others are null.
I'm using mySQL and Sequelize to back up the Users service.
Addressing your side note first:
On a side note, I wanted to ask if the Authentication 1.0.2 should populate the Users' table with more than the {provider}Id column. Currently I can only see the that column getting populated while the others are null.
OAuth plugins will populate a column that matches the provider name. So with GitHub auth, you'd see a github
attribute.
@muvhaus-sl I'm not experienced with React Native, yet. Last month we created the feathers-authentication-popups module that helps handle the auth workflow for web apps. We've discussed how to use it for React Native, but we have yet to implement it. It's one of the very next things on the list, though. Maybe @ekryski can pitch in when he returns.
The reason that it only adds providerId
is that we had problems with databases that require the data format to be pre-defined (e.g. all *SQL dbs). In that case you'll have to map the information you want from the oAuth callback to the fields you defined anyway.
@daffl ,
Is there any example that I can refer to?
Sorry if I'm being too demanding. It has been a very steep learning curve to get where I am, hopefully I can gain understanding and contribute back soon enough.
@marshallswain
On react native, the CookieManager setup that has been mentioned a few times on other issues works fine. I got Facebook to work fair enough.
So far, only Google has blocked the usage of Webviews for OAuth and I don't think its for technical reasons, I think it has been just another corporate move...
Perhaps the viable solution would be for the feathers client to have a widget like Auth0's lock? However, I must say, its a lot of trouble and I think it is not part of the scope of what Feathers is trying to do?
I don't think there is a clean way of doing a client side "widget" if OAuth providers start having different requirements...
experiencing the same issue as in the original question. In my case, I'm trying to migrate the 0.7.x /auth/local
route to using /login
, as described by the line in the README.
app.post('/login', auth.express.authenticate('local', { successRedirect: '/app', failureRedirect: '/login' }));
The redirect part works. However, after trying for a couple hours I too couldn't get the authToken to show up in the cookie after redirect. Tried manually setting auth.express.setCookie
as a middleware to the post request and passing in app.get('auth')
but to no avail. Putting a logger into the actual setCookie middleware revealed that it was never being called during a "normal" login flow where a user posts to /login
.
All of that to say, I'm pretty confused
@jiangts, Do you have this on your configuration:
"cookie": {
"enabled": true,
"name": "feathers-jwt",
"httpOnly": false,
"secure": false, //this is not very safe for production usage, but I dont have https for testing.
"domain": "my domain" //replace with your actual domain or IP address
},
Then, the other thing I did to find out the reasons was to set the debug flag for "feathers-authentication*" In doing that, I could spot where the setCookie was stopping.
Hi @muvhaus-sl,
I followed your suggestion and got the following log:
feathers-authentication-local:verify Checking credentials +22ms <email> <password>
feathers-authentication-local:verify user found +13ms
feathers-authentication-local:verify Verifying password +1ms
feathers-authentication-local:verify Password correct +205ms
feathers-authentication:passport:authenticate 'local' authentication strategy succeeded +0ms { _id: 586d446040ccc98452435a84,
email: '<email>',
password: '<pw hash>',
__v: 0,
updatedAt: 2017-01-04T18:52:16.385Z,
createdAt: 2017-01-04T18:52:16.385Z } { userId: 586d446040ccc98452435a84 }
feathers-authentication:express:authenticate Redirecting to / +1ms
feathers-authentication:express:expose-headers Exposing Express headers to hooks and services +4s
feathers-authentication:express:expose-cookies Exposing Express cookies to hooks and services +0ms undefined
Still not sure where setCookie failed...
Did you get something similar?
@jiangts How is your auth config looking like? And, do the Cookie domain and the actual host name match? I would assume that if your cookie domain is set to "something.com" and you are accessing the server as "127.0.0.1" or "localhost", then the browser will not get the cookie?
I did not use Express, so the Expose-cookies is not triggered. On my case it uses Set-cookies.
@muvhaus-sl
I figured it out. However, I haven't gotten a flow that's as easy/simple as the version in 0.7.x with both setting the cookie and also redirecting the user. In fact, I had to write quite a bit of code on my own instead of just "glue" code over feathers-auth. I'm not sure if this is intentional for such a common use case (just a normal login), or maybe I'm missing something, but in either case, this wasn't anything like pleasant experience I've had so far with feathers :(
For future people, my solution was to create a new service called /login
and put auth.express.setCookie(app.get('auth'))
as a middleware to that service. This gets the cookie set properly. Then, I wrote some of my own code to do the redirect and clearing the cookie in case of auth failure, as auth.express.successRedirect
and auth.express.failureRedirect
didn't quite do what I wanted.
So @marshallswain can probably explain this much better than me but the problem of allowing the JWT in a cookie for all API endpoints (and why it has been removed) is that the API will become vulnerable to Cross Site Request Forgery (CSRF), so if someone hijacks a users browser they can make any authorized requests to the API. This is why only specific routes should be explicitly enabled for cookies. Maybe @ekryski can weight in if there is something else that can be done better for your usecase though.
@muvhaus-sl @jiangts can you post a link to a github repo? Quite frankly, there might be a bug in setting the cookie but it also might be that you just have things set up a different way. @jiangts the intention is for it to work the same as 0.7.x but there are a few more scenarios that we are covering now so we have those extra guards in the set-cookie middleware.
I also was having a similar issue. Perhaps I am misunderstanding something. One of the purposes of enabling cookies is to have non-SPAs (old school apps) be able to authenticate without having to set up tokens in the header, correct? Otherwise I don't see the purpose of the cookie being saved in the browser and resent along with every request. And yet you say that due to CSRF cookies are disabled on all routes (if I'm understanding you.) I thought that enabling the cookie option in the config allows cookies on all routes, and the authentication schemes/providers should be able to read it like normal.
When I use the provided middleware, a cookie does NOT get set (both cookies and sessions have been enabled, confirmed via debugging):
app.post('/login', auth.express.authenticate('local', { 'successLogin: '/', failureRedirect: '/login' }));
I have also tried something like the following:
app.post('/login', function(req, res, next) {
return app.service('authentication').create({
email: 'test@test.com',
password: 'asdfasdf'
}).then(function (result) {
console.log('Authenticated with result: ', result);
next();
})
});
Additionally, I've tried setting up the setCookie middleware manually to no avail:
app.post('/login', auth.express.authenticate('local', { failureRedirect: '/' })); // note, no successRedirect, to get to the cookie middleware
app.post('/login', auth.express.setCookie(app.get('auth')));
app.post('/login', function (req, res) {
res.send('DONE!');
});
If I do a POST request directly to the authentication
service endpoint, I get a token AND a cookie set properly, so I know cookies are being set OK, just not with regular express routes.
Further digging shows that the setCookie middleware gets to this point:
if (res.hook && res.hook.method !== 'remove' && res.data && res.data.accessToken) {```
There is no hook
property on the response, and this is where I'm stuck -- I can't figure out where hook
gets added to the response. I've been at this the entire day, and I don't know what else to try.
My apologies if this should be obvious, I'm quite new to Express, let alone feathers. But nowhere in the documentation do I see where hook
gets set, nor how cookies can be used with regular routes.
Thanks in advance.
EDIT: Just noticed that even with the cookie being set properly if using the standard /authentication
service, the following calls to a protected route, e.g. with find: auth.hooks.authenticate(['jwt', 'local']),
, authentication fails with NotAuthenticated: No auth token
. Logs show that both local and jwt methods failed--they seem to ignore the cookie.
@eblanshey
My original issue had the same symptom as yours: set-cookie.js would stop at the condition.
res.hook
With res.hook being undefined.
For a speedy solution, I commented that part of the condition and it looks like everything is working as expected. I'm also not sure why it was undefined to start with.
If I understood it right, on the
auth.hooks.authenticate(['jwt', 'local'])
You need to include 'cookie' on that array. I had to include "facebook" if I wanted to allow facebook authentication, for instance. And as far as I understand, cookie becomes a strategy if enabled, so you need to allow it on the list of possible "authenticate" strategies.
Looking forward for a more elegant solution on the set-cookie, as well as some corrections from the more experienced people when it comes to the setup.
@muvhaus-sl unfortunately adding "cookie" or "cookies" to the array doesn't work: Unknown authentication strategy 'cookie'
. I think the functionality is simply broken.
I did a quick fix by adding a middleware that adds the Authorization header from the value of the cookie (using cookie-parser library):
app.use(function(req, res, next) {
let cookieValue = req.cookies[app.get('auth').cookie.name];
if (typeof cookieValue === 'string') {
req.headers.authorization = "Bearer "+cookieValue;
}
next();
});
As for actually setting the cookie, since that only needs to be done once on one route, I'll just make the login an ajax request to /authentication
.
To the core devs: I understand feathers is mainly geared towards creating APIs, but full support and documentation for "old school" apps would great, especially since the docs guide you through how to set up view engines and what not.
My use case for using feathers is to quickly create a server-side rendered app, under the hood using the services to fetch data. That way I'd have a fairly easy roadmap to changing it to an SPA down the road when I have more time (just using the service endpoints that the traditional routes use). This could be a great selling point for devs to use Feathers for their next project. As it stands now, authentication using traditional methods is very difficult to understand. I suppose adding built-in CSRF protection would be needed, which could be a matter of using https://github.com/expressjs/csurf and automatically applying it to all non-service routes.
I believe I have got the same issue as seeing the following debug statement on my feathers server, and my cookies are not being set following a successful authentication:
feathers-authentication:express:expose-cookies Exposing Express cookies to hooks and services +0ms undefined
My auth
config has the following set for cookies:
"cookie": {
"enabled": true,
"name": "feathers-jwt",
"httpOnly": false,
"secure": false
}
@eblanshey
One of the purposes of enabling cookies is to have non-SPAs (old school apps) be able to authenticate without having to set up tokens in the header, correct? Otherwise I don't see the purpose of the cookie being saved in the browser and resent along with every request. And yet you say that due to CSRF cookies are disabled on all routes (if I'm understanding you.) I thought that enabling the cookie option in the config allows cookies on all routes, and the authentication schemes/providers should be able to read it like normal.
I'm really short on time, today, so this is going to kind of come out as a stream of consciousness, but I'll do my best.
It's pretty common to conflate enabling cookies with turning on "old-school" auth for routes. With Feathers auth we separate the two. Turning cookies on in the config just creates a cookie. It doesn't automatically enable old school, CSRF-vulnerable routes.
It's important to keep the SSR server and API server as distinct entities. The SSR server can pull the JWT from the cookie, decode it to get the payload. This will include whatever entity data you've populated inside the JWT (like userId
). You can then use this data inside the params
import decode from 'jwt-decode';
app.use(function(req, res, next) {
let jwt = req.cookies[app.get('auth').cookie.name];
let payload = decode(jwt);
// Only use service.find and service.get for GET requests
app.service('todos').get({query: {}, user: payload.userId})
.then(response => {
// Use the response data and assemble your HTML.
});
});
You probably don't want to expose that cookie value to be used by the rest of the API server. At least, I wouldn't want to deal with CSRF at all. Even a good CSRF library is only going to mitigate CSRF attacks, not eliminate them. If you keep the cookie auth data away from the API server, you've eliminated the risk of CSRF completely. If your goal is to enable SSR it's just not necessary to expose cookie auth data to the API server. The example above works more like an auth proxy to get authenticated data from the server in order to form HTML content. (You'd want to make sure CORS is disabled for SSR routes)
We chose to keep the API server distanced from cookie auth because most people aren't aware of the risks or how to mitigate them. And in most cases I've seen where people want to turn cookie support on, there's an alternative method that doesn't introduce security or privacy issues. From my perspective, an API server should probably only expect a cookie for auth as an additional check on top of requiring a header. Otherwise, API servers shouldn't be able to consume auth data from a cookie.
Running into the same issue as well upgrading a React Native app from feathers-auth 0.7.x to 1.0. The app uses the cookie from an OAuth webview flow purely to obtain and store the JWT. Subsequent calls to services use sockets and bearer token authentication.
Is it possible to have the cookie set just for the /auth/{provider} request and subsequent redirect?
I'll take a look at this today. There must be a bug somewhere as you should be able to send back a cookie with the JWT in it and parse it your React Native webview and set it back in AsyncStorage yourself. So long as you are making calls after all that is completed it should work.
You should NOT be relying on the JWT inside the cookie to authenticate with your app. (ie. client sends cookie with JWT in it and server verifies it from there). In order to do that you need to enable the CookieExtractor
provided by passport-jwt explicitly https://github.com/feathersjs/feathers-authentication-jwt#extractjwt.
A simple reproducable example from someone sure would be helpful... 😁
For myself, a working chat app update would answer many questions. Using the standard chat app from the Feathers book, I attempted to update to auth 1.0.2 and related packages using the migration guide. #https://github.com/feathersjs/feathers-authentication/issues/386
Here is my services/authentication/index.js:
'use strict';
const auth = require('feathers-authentication');
const local = require('feathers-authentication-local');
const jwt = require('feathers-authentication-jwt');
module.exports = function() {
const app = this;
let config = app.get('auth');
app.configure(auth(config))
.configure(jwt())
.configure(local())
app.service('authentication')
.hooks({
before: {
// You can chain multiple strategies on create method
create: auth.hooks.authenticate(['jwt', 'local'])
}
});
};
I get this in the body when I authenticate: {"accessToken":"eyJhbGciOiJIUzUxMiIsInR5cCI6ImFjY2VzcyJ9.eyJ1c2VySWQiOiJOaUtlb3E2cTQ4cU1Sa0paIiwiaWF0IjoxNDg2NTg2NzgzLCJleHAiOjE0ODkxNzg3ODMsImF1ZCI6Imh0dHBzOi8vMTI3LjAuMC4xIiwiaXNzIjoiZmVhdGhlcnMiLCJzaXYBOiJhbm9ueW1vdXMifQ.tTYovuN39PMJxCmK6-I_vX9-LP6vKSa7Gu4ttYAZioD0ifiSOamcmTs4hz3nlPGu5wGBkTTtNSZDuPrOxKNTKQ"}
tldr; I can get token, what do I do with it to validate the logged in user on subsequent routes/navigations?
I'm authenticating users with front-facing login form /
, then redirecting to a main home page /main
.
My set up is a little complicated (MySQL + Feathers + VueJS SSR (really pre-rendering, but I haven't gotten that to work so I'm just using express-vue
) + Angular), but that shouldn't matter much.
I can use the feathersClient to authenticate users, but I don't know what to do with the response. I can authenticate, but then if I manually go to a page that is behind an auth check it fails. I assume this is because cookie check fails?
This is my default.json
's auth
property.
"auth": {
"secret": "...bigsecret...",
"strategies": [
"local",
"jwt"
],
"path": "/authentication",
"service": "users",
"jwt": {
"header": {
"type": "access"
},
"audience": "localhost", // TODO probably need to programatically get this...?
"subject": "anonymous",
"issuer": "feathers",
"algorithm": "HS256",
"expiresIn": "1d"
},
"local": {
"entity": "users",
"service": "users",
"usernameField": "username",
"passwordField": "password"
},
"cookie": {
"enabled": true,
"domain": "localhost",
"name": "feathers-jwt",
"httpOnly": true,
"secure": false // TODO make sure this is true in prod
}
}
Here is my authentication service and hooks
const authentication = require('feathers-authentication');
const jwt = require('feathers-authentication-jwt');
const local = require('feathers-authentication-local');
const manage = require('feathers-authentication-management');
module.exports = function() {
const app = this;
const config = app.get('auth');
const managedConfig = app.get('managedAuth');
// Set up authentication with the secret
app.configure(authentication(config));
app.configure(jwt());
app.configure(local(config.local));
app.configure(manage(managedConfig));
app.service('authentication').hooks({
before: {
create: [
authentication.hooks.authenticate(config.strategies)
],
remove: [
authentication.hooks.authenticate('jwt')
]
}
});
}
From the client's perspective, am I supposed to be able to use feathersClient to authenticate and then future navigation just works? Because this is not so 😢
I add routes with a function like so (I can just pass this to a forEach over an array of routes, makes it trivially easy to add new routes)
let buildRoute = r => {
app.use(
'/' + r.path,
auth.express.authenticate('jwt'),
(req, res) => {
res.render('main', routeData[r.name]);
}
);
};
Ideally, wouldn't the feathers server set the header for the client? I'm at a loss of what I'm supposed to do...
To repro - set up normal feathers app, latest authentication - I'm using username and password as opposed to email (and only that, no OAuth for this project) - client landing page is /
which is login form (don't even need to set up html, can just use chrome debugger), then use feathers client to get back the token. Clicking a link to /main
or manually navigating to /main
or even location.href= /main
all don't work, even though the server should know this user is authenticated. I don't even know if this is supposed to work though...
I think I'm just missing something though... This should be easy, right? Logging users in? 😭
I found these two issues, so I think I'll have something to try tomorrow. I'm probably just doing something wrong.
Is there any update on the topic?
I see it started from the react native
, but I use regular react
and have the issue that after the auth
any cookie is set.
https://github.com/feathersjs/feathers-authentication-local/issues/17#issuecomment-302887815
not sure if the issue is the same, but I assume so.
Do I understand right, that the use case of the cookie jwt
is, that after the page is refreshed manually - customer doesn't need to authenticate himself again?
Regards,
If you're going to use the feathers client with Socket.io to make requests, authentication has to happen on every refresh. This is a limitation of the socket auth.
@marshallswain
yes, thanks, but if the cookie is set and the token is still valid, the authentication
could be more or less "automatic".
therefore the fact of setting the cookie
automatically is important.
I also tracked that this piece of code: https://github.com/feathersjs/feathers-authentication/blob/master/src/express/set-cookie.js#L14
is never called (in my case at least), however the setCookie
is registered.
Correct. We left it out on purpose to avoid CSRF attacks. You have to manually register it. The cookie is still useful in SSR scenarios without that middleware enabled, so it's not enabled automatically when you turn on the cookie option.
Ah that's the case.
@marshallswain could you, please, confirm that I understood it right.
The feathers-jwt
cookie would be set (on the client) after the correct authentication only in case I register the express middleware explicitly? Like it's described here: https://docs.feathersjs.com/api/authentication/server.html#express-middleware .
And the option cookie: {enabled: true}
is not enough?
Oops. I meant the expose-cookie middleware. set-cookie should run with the options you posted. On a development machine, you'll need these options to get a cookie:
enabled: false, // whether the cookie should be enabled
name: 'feathers-jwt', // the cookie name
httpOnly: false, // whether the cookie should not be available to client side JavaScript
secure: false // whether cookies should only be available over HTTPS
That middleware should run on successful authentication. Do you have your before hooks on the authentication service setup?
@marshallswain yes, I think I have everything set.
here I describe more about the issue: https://github.com/feathersjs/feathers-authentication-local/issues/17#issuecomment-302887815
Regards,
It looks like this issue has become a little bit of a kitchen sink of authentication cookie issues but I think the main issue of authenticating Express routes by storing the JWT in the cookie has now been addressed and there is a guide at https://docs.feathersjs.com/guides/auth/recipe.express-middleware.html
First, sorry if I've got things wrong. This is my first project with feathersJS/react native...so I'have been struggling a bit, and trying to make things work.
I have been trying to get OAuth2 (Facebook) and Local authentication to work using the latest feathers-authentication(1.0.2)
I managed to get up to a point where I would see the accessToken being delivered as body of a GET request to the server, in the format as follows: http://{my local dev server}/auth/facebook/callback?code={ a facebook generated code here...}#=
The accessToken, when decoded using JWT does include userID and the other properties. So, after I got this right, I tried to use it on the React Native App. The sample I found (https://github.com/sscaff1/hopePing) used CookieManager, for the simple reason that we cannot get the content of the response (inside the WebView) and we cannot run "fetch" on the same URL. I did not understand how to setup a "successRedirect"...so I went on to enable Cookies. This is how my default.json looks like:
But then, the cookie would not show up on Chrome, no matter how I changed any of the above Cookie settings.
I set the Debug=feathers-authentication* and then I realized the last log I got for the Set-Cookie.js was debug('Running setCookie middleware with options:', options);
I've put a temporary log after: if (options.enabled && options.name) { and it would show.
I pinpointed that the following condition was not met: if (res.hook && res.hook.method !== 'remove' && res.data && res.data.accessToken) {
res.hook was undefined. If I remove res.hook && res.hook.method!=='remove', it works as expected, but I get 2 cookies for the same name/content.
Sorry for the long text, I hope I have explained everything properly. I wanted to know if I'm doing something wrong.
thanks in advance.