I have a Koa server that uses Passport to authenticate users against an Array,
and a React client. After successful login, the following requests are not authenticated as the cookie is undefined. The authenticate function's error parameter has:
{ message: 'Missing credentials' }
After browsing the site, I fixed the usual errors, calling the returned function of authenticate, adding {credentials: 'include'} to fetch etc, but still I have the same problem.
Middleware list:
router.use(cookie.default());
app.use :
koa-body, koa-session-store (also tried with koa-session), passport.initialize(), passport.session(), router.routes(), koa-static
local strategy
passport.use(new Strategy((username,password,callback)=>{
var u = users.find(u=>u.username == username);
return (u && password == 'password')? callback(null, u ):callback('user not found', false);
}));
/login authenticate
.post('/login', (ctx)=>{
console.log(ctx.request.body);
return passport.authenticate('local',(err,user,info,status)=>{
if(user) {
ctx.login(user);
ctx.body = {success: true}; // works correctly
ctx.redirect('/login-success');
} else {
ctx.redirect('/login-failure');
}
})(ctx);
});
/login-success
router.get('/login-success',async(ctx)=> {
return passport.authenticate('local',(err,user,info,status)=>{
console.log(err); // "Missing credentials"
})(ctx);
await ctx.response;
ctx.body = {success: true};
}).
Client call
let body = JSON.stringify({username: this.state.username, password: this.state.password});
let result = await fetch('http://localhost:4200/login',{method:'POST',credentials: 'include',body, headers:{'Content-Type':'application/json'}});
The fix is actually simple, but the reason is hard to find.
async middleware must either call await next() or return next() at the end.
Otherwise a 404 error is caused.
Adding await next() to the async /login-success callback, fixed the issue.
Documentation: https://github.com/koajs/koa/blob/master/docs/troubleshooting.md#my-middleware-is-not-called
Related
I am trying to write a middleware that extracts the user model and attach it to the request pipeline.
I have already written a token extractor middleware and managed to attach the token to the request pipeline, but for some reason when I try to extract the user model, it works fine inside the middleware function yet inside my controller it returns as undefined.
Here's what I have tried:
utils/middleware.js
const tokenExtractor = async (request, response, next) => {
const authorization = await request.get('authorization');
if (authorization && authorization.toLowerCase().startsWith('bearer ')) {
request.token = authorization.substring(7);
} else{
request.token = null;
}
next();
};
const userExtractor = async (request, response, next) => {
tokenExtractor(request, response, next);
if(request.token){
const decodedToken = jwt.verify(request.token, process.env.SECRET);
request.user = await User.findById(decodedToken.id);
console.log(request.user); // Works
next();
} else{
response.status(403).json({ error: 'no token received' });
}
};
Inside my controllers it breaks down:
controllers/blogs.js
blogRouter.post("/", async (request, response, next) => {
if (request.body.title && request.body.url) {
const token = request.token;
if (!token) {
return response.status(401).json({ error: 'invalid token' });
}
console.log(request.user); // undefined !
if(!request.user){
return response.status(401).json({ error: 'invalid user' });
}
const user = request.user;
const blog = new Blog({
title: request.body.title,
author: request.body.author,
url: request.body.url,
likes: request.body.likes,
user: user._id,
});
await blog.save();
user.blogs = user.blogs.concat(blog._id);
await user.save();
response.status(201).json(blog);
}
response.status(400).end();
});
Both middleware are already attached to the express app.
EDIT:
I have fixed the issue by removing the call to tokenExtractor from userExtractor function, and then chaining the middleware to the router instead of calling them before everything.
I was using the tokenExtractor globaly, while the userExtractor locally to the blogsRouter. What was happening was that while the tokenExtractor was working fine, the blogRouters was being called before the userExtractor ever get called, hence why I was getting undefined.
app.js
// app.use(tokenExtractor);
app.use(requestLogger);
app.use(errorHandler);
// app.use(userExtractor);
app.use('/api/login', tokenExtractor, loginRouter);
app.use('/api/users', usersRouter);
app.use('/api/blogs', tokenExtractor, userExtractor, blogRouter); // chaining the extractors
It makes sense, let next() carry the (req, res, next) instances forward, as a pipe. No hacks are needed and you can stack as many middlewares as needed and even reuse values from one inside the other - if you can trust the order of the call stack.
You don't need to chain it. The callback argument for the next middleware function only needs to be specified as follows.
const tokenExtractor = (request, response, next) => {
const auth = request.get('authorization')
if (auth && auth.toLowerCase().startsWith('bearer ')) {
request.token = auth.substring(7)
next()
} else {
next()
}
}
I've been struggling to do this for about 6 days...
Everything is working perfectly such as authorization but one problem I had is making authentication.
On my user model (for creating the database schema) I do have a way to generate a token for logged in users or registered.
userSchema.methods.generateAuthToken = function(){
const token = jwt.sign({ _id: this._id }, config.get('jwtPrivateKey'));
return token;
}
So when user post to /login, server will respond with a token:
router.post('/', async (req, res) =>{
// Here i'm validating data and then if everything is right the code under will run.
console.log('logged in as: ' + user.username);
// Here i'm using the function to generateAuthToken().
const token = user.generateAuthToken();
console.log("Token from server: " + token);
// now here is my main problem i would like to use cookies to store it for an hour or so.
// then client can send it back to server for protected route.
res.status(200).send(token);
});
I have made a middleware function for auth (to check the token if you're going through a protected route)
module.exports = function (req, res, next){
// instead of using headers i would like to check for the cookie value if it's the token,
// pass the user in, else Access denied.
// I have no idea how to use cookie parser with middleware functions.
const token = req.header('x-auth-token');
if(!token) return res.status(401).send('Access denied. Sign in or register.');
try{
const decoded = jwt.verify(token, config.get('jwtPrivateKey'));
req.user = decoded;
next();
}
catch(err){
res.status(400).send('Invalid Token!');
}
}
here i'm using the auth middleware function:
const express = require('express');
const router = express.Router();
const auth = require('../middleware/auth');
// but it's actually not passing the user in since i haven't done it with cookies.
router.get('/', auth, (req, res) =>{
res.render('index', {});
});
I do know I can do it with localStorage but it's a terrible practice and it would be better to store it on cookies so no one could hack on.
Is there any good approach to solve this problem? I'm kinda lost and lost hope to go back to sessionID (which I don't want to :( ).
After you request on frontend, you need get the response (token) and save on browser using this for example:
fetch('http://your-api-host/login', {
method: 'POST',
body: {
username: "user1",
password: "passworduser"
}
})
.then((res) => res.text((res)))
.then((token) => {
document.cookie = `AUTH_API=${token}`; <-- this save the cookie
})
With this value saved on frontend you need send this information on all requests, it's commum send this value on your HEADER (how you makes), to save on header you need read the value from token and put on header, like this:
const headersTemp = document.cookie.split(';'); // <-- this get all cookies saves and splits them in the array.
const finalHeaders = {};
headersTemp.forEach((header) => { // <-- looping on all cookies
const headerTemp = header.split('='); // <-- split each cookie to get key and value
finalHeaders[headerTemp[0].trim()] = headerTemp[1].trim() // <-- save on object to access using keys.
})
Now you can access all cookies using the key (the same used before), I used the key AUTH_API to save my cookie, let's send the request using fetch api:
fetch('http://your-api-host/route-protected', {
method: 'POST',
headers: {
'x-auth-token': finalHeaders['AUTH_API']
},
})
If you creating your application using libraries how React or any SPA framework, probably you will use tools like Axios, and I recommend uses libraris how This, it's more easy to work with cookies.
My code:
const model = require('../db/models/user');
const describe = require('mocha').describe;
const assert = require('chai').assert;
const chaiHttp = require('chai-http');
let chai = require('chai');
let server = require('../server');
chai.use(chaiHttp);
describe('Test user registration, login, update password', () => {
beforeEach((done) => {
// Reset user mode before each test
model.User.remove({}, (err) => {
console.log(err);
done();
})
});
Now, I get the error
UnhandledPromiseRejectionWarning: TypeError: Cannot read property
'_id' of null
in the route itself, specifically:
router.put('/me/update-password', async (req, res, next) => {
const {body} = req;
const auth = req;
const userId = auth._id; // problem on this line!
// rest of code...
});
So, after registration and logging in (which works fine, as it should!), I am having a lot of problems to update the password. In the params I am sending generated token and in the body is the password field with new password. On live example (for example Postman) it works as it should, but in tests it simply does not.
I really have no idea and have lost a lot of my time over this already (3 days).
Can someone please take a look suggest solution?
Much appreciated.
Updated with auth.js:
const jwt = require('jsonwebtoken');
const isAu = function(req) {
return jwt.verify(req.headers.authorization.split(' ')[1], 'secret', function (err, decoded) {
if (err) {
return null;
}
return decoded;
});
};
module.exports = isAu;
EDIT:
Since OP changed the original question after it has been answered here is the link to original: https://stackoverflow.com/revisions/55064109/1
=======================================
JWT verify method accepts Authorization token - you are fetching that correctly by splitting Authorization header string in order to fetch token.
HTTP Authorization header string hold Authentication scheme type (Bearer, Basic, Digest, etc) and the token value
Authorization: Bearer eyJhbGciOiJIUzI1NiIXVCJ9...TJVA95OrM7E20RMHrHDcEfxjoYZgeFONFh7HgQ
but your Authorization header in the Chai request only holds the value of the token and not the Authentication scheme type.
Assumin your Authentication scheme is Bearer you need to set that in your Chai request Authorization header:
...
chai.request(server)
.put('/api/me/update-password')
.set('Authorization', `Bearer ${token}`)
.send(`${updatedPassword}`)
.end((error, response) => {
assert.equal(response.status, 200);
done();
});
...
On the other hand, in case you do not specify Authentication type in the request authorization header than you should send it like that to JWT to veirfy:
const isAuthenticated = function(req) {
return jwt.verify(req.headers.authorization, 'secret', function (err, decoded) {
if (err) {
return null;
}
return decoded;
});
};
My application is a Node.js API with a client inside the same application.
I'm trying to implement a simple auth login that uses a JWT token generated by a Node.js API.
My logic is as follows:
Client: User submits login information to /auth/login route.
$.ajax({
url: "/auth/login",
type: "POST",
data: formData,
dataType: "json",
success: function(data, textStatus, jqXHR) {
if (typeof data.redirect == "string") {
window.location = data.redirect;
}
},
error: function(data) {
if (typeof fail === "function") fail(data);
}
});
API: Verify user and on success generates JWT and sends back to the client.
router.post("/login", async (req, res) => {
var login = { UID: req.body.UID, password: req.body.password };
AU.manualLogin(login)
.then(result => {
res.header("x-auth-token", result.token).json({
status: 200,
message: "success",
data: result.data,
redirect: "/dashboard"
});
})
.catch(err => next({ status: 400, message: err.message }));
});
Client: Saves JWT to the header and checks for redirect - In this case, I use window.location to direct to /dashboard after successful login. (this part I'm not sure about)
API: Middleware checks valid JWT on protected routes.
module.exports = function auth(req, res, next) {
const token = req.headers["x-auth-token"];
if (!token)
return res.status(401).send("Access denied. No token provided.");
try {
const decoded = jwt.verify(token, "jwtPrivateKey");
req.user = decoded;
next(); //pass control to next middleware
} catch (ex) {
res.status(400).send("Invalid token.");
}
};
The Problem:
The token is definitely being sent from API -> Client. But I have no idea how to handle the token from the client-side. I think the issue might be to do with the window.location redirect as at this point it does not seem to be sending the x-auth-token to the API.
What I have tried
I have tested the solution with Postman from end-to-end and it works fine. That probably proves that it isn't the API side that has the issue.
I've also tried these sources:
Pass request headers in a jQuery AJAX GET call
Adding custom header in HTTP before redirect
How to add header to request in Jquery Ajax?
jwt on node - how does the client pass the token back to the server
You need kind of a storage to keep the token. Otherwise the user has always to login again after he closes the browser/tab. So it's quite common to keep the token in local or session storage.
Approach 1: Use a single page application (SPA) framework like angular, vue.js, react etc. to protect your routes client-side
Approach 2: You can request only html and css (view) from your backend and then store the token after a login procedure. With a valid token, fetch the (protected) data with ajax requests. Redirect to the login page if a ajax request returns the status code 401 (unauthorized) or a user wants to access the protected route without having a token stored. This is perhaps the most suitable for you.
Approach 3: Use Node.js with a backend framework like express and store auth information in a server side session
index.js
const express = require('express');
const session = require('express-session');
const app = express();
app.use(require("cookie-parser")());
app.use(session({ secret: 'aslwezoweasdfasdlkfalksdfhweelaerfcv', resave: false, saveUninitialized: true}));
routes/protectedRoutes.js
const express = require('express');
const router = express.Router();
router.all("/*", util.handleAuthenticate); // check auth on every request
// other routes
indexController.js (login functionality)
module.exports.login = function(req, res) {
if(!req.session.name) {
// check username/password --> db lookup
// if valid:
req.session.name = ...
// redirect to home or backref
// else: redirect to login
}
}
util/security.js
function isLoggedIn(req) {
return !!req.session.name;
}
function handleAuthenticate(req, res, next) {
if(isLoggedIn(req))
{
next();
}
else
{
// redirect to login page
}
}
I have got a middleware like this
// route middleware to verify a token
app.use(function(req, res, next) {
console.log(req.baseUrl);
// check header or url parameters or post parameters for token
var token = req.body.token || req.query.token || req.headers['x-access-token'];
// decode token
if (token) {
// verifies secret and checks exp
jwt.verify(token, app.get('superSecret'), function(err, decoded) {
if (err) {
return res.json({
success: false,
message: 'Failed to authenticate token.'
});
} else {
// if everything is good, save to request for use in other routes
req.decoded = decoded;
next();
}
});
} else {
// if there is no token
// return an error
return res.status(403).send({
success: false,
message: 'No token provided.'
});
}
});
This route http://localhost:8080/verifyAccount doesn't responds as No token provided
app.get('/verifyAccount', function (req, res) {
res.json({ message: 'Welcome to verify account!' });
});
But the following route http://localhost:8080/verifyAccount?id=123 does:
app.get('/verifyAccount/:id', function (req, res) {
res.json({ message: 'Welcome to verify account!' });
});
The middleware code is at the bottom in the code file and the get paths are upwards
What is that concept behind?
Why adding a get parameter forces middleware execution?
Just found that if I call it like this http://localhost:8080/verifyAccount/id=123, it properly returns Welcome to verify account!
Found that the issue was in the way by which the route was getting called. Thanks to Thomas Theiner for the help.
The request with query parameter ?id=123 does not match with /:id. It should be called as verifyAccount/123 instead.
Since, the route ?id=123 did not matched any of the path. Hence, was finally reaching the middleware for execution
The position determines the parameter, not the name. The name is only used inside node code to reference the parameter.
For multiple parameters, we'll have multiple slashes like verifyAccount/:id/:otherParameter which would be called using verifyAccount/123/234