I need to test protected routes with mocha and chai. The authentication middleware crashes all the time with an error 500 because jwt must be a string.
When the user logs-in, he receives an http-only cookie whose data is a jwt token. The cookie is automatically sent to the server on each request thanks to axios withCredentials: true property.
So, I've tried to set a cookie or an auth bearer to my test, but without any success so far. How to fix this?
Here is what I've written:
import chai from "chai";
import chaiHttp from "chai-http";
import { app } from "../index";
import jwt from "jsonwebtoken";
chai.use(chaiHttp);
const api = chai.request(app).keepOpen();
const token = jwt.sign(
{ _id: "123", locale: "en" },
process.env.JWT_TOKEN_KEY,
{
expiresIn: "14d",
}
);
describe("GET /user/:id", () => {
it("return user information", (done) => {
api
.get("/user/123")
.set("Cookie", token)
.end((err, res) => {
chai.expect(res).to.have.status(200);
done();
});
});
});
The middleware is:
import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
import { Cookie } from "../models/Cookie";
interface Authenticate extends Request {
cookie: Cookie;
cookies: any;
}
const authenticate = (req: Authenticate, res: Response, next: NextFunction) => {
const token = req.cookies;
if (token) {
jwt.verify(token, process.env.JWT_TOKEN_KEY, (error, res) => {
if (error) {
return res.sendStatus(403);
}
req.cookie = { _id: res._id, locale: res.locale };
return next();
});
}
return res.sendStatus(401);
};
export default authenticate;
I've been stuck on testing my api routes since 48 hours. Any help would be highly appreciated.
req.cookies is an object, from the express docs:
When using cookie-parser middleware, this property is an object that contains cookies sent by the request. If the request contains no cookies, it defaults to {}.
So you need to set the jwt inside the cookie in this way:
.set('Cookie', `jwtToken=${token}`)
And get it in your middleware:
const { jwtToken } = req.cookies;
jwt.verify(jwtToken, ...
Related
I hope someone can help me! I am trying to create a register/login form, and doing so, I was thinking to save the JWT token in cookie session. Even if in Postman everything is ok, every time that I try to get back cookies in orderd to take the take and verify if it exists to protect the route, I always get "undefined"! I'm going crazy.
FILE SERVER.TS: Here i call the method use() in order to mount the middlewars
//ALL IMPORT HERE
const port = env.PORT
const path = "/users"
const url = env.URL
const server = express()
server.use(express.json())
server.use(cookieParser())
server.use(policyCors())
server.use(path, router)
THIS IS THE LOGIN MIDDLEWARE:
router.post(login, async(request: Request, response: Response, next: NextFunction) => {
const { email, password, } = request.body
const user = await prisma.users.findFirst({
where: { email: email }
})
if (user && await bcrypt.compare(password, user.password)) {
const payload = { email }
const token = jwt.sign({ email: user.email }, "String(secret)", { expiresIn: "30m" })
response.cookie("token", token, {
maxAge: 60*60*24*30*1000
})
response.json({ token: token })
}
})
This is the token verifier function:
const authToken = (request: Request, response: Response, next: NextFunction) => {
const accessToken = request.cookies["token"]
console.log(accessToken)
next()
}
router.get("/account", authToken, (request: Request, response: Response, next: NextFunction) => {
response.json("ok")
})
I expect to receive back the token from the cookie
I have a middleware that checks if a user is loggued-in before letting him access a route. It looks like this:
import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
const authenticate = (req: Authenticate, res: Response, next: NextFunction) => {
const token = req.cookies;
if (token) {
jwt.verify(token, process.env.JWT_TOKEN_KEY, (error, res) => {
if (error) return res.sendStatus(403);
req.cookie = { _id: res._id, locale: res.locale };
return next();
});
}
return res.sendStatus(401);
};
export default authenticate;
The test is:
import chai from "chai";
import chaiHttp from "chai-http";
import { server } from "../index";
chai.use(chaiHttp);
const api = chai.request(server).keepOpen();
describe("GET /user/:id", () => {
it("return user information", () => {
api
.get("/user/123")
.set("Cookie", "_id=567;locale=en")
.end(function (err, res) {
chai.expect(res).to.have.status(200);
});
});
});
The test works fine if I remove the middleware from the route. So:
// OK
router.post("/user/:id", searchUser);
// NOT OK
router.post("/user/:id", authenticate, searchUser);
The error is:
TypeError: Cannot read property 'sendStatus' of undefined
at /Users/myname/Desktop/Code/myapp/server/src/middleware/authenticate.ts:21:29 at Object.module.exports [as verify] (/Users/myname/Desktop/Code/myapp/server/node_modules/jsonwebtoken/verify.js:57:12)
The res variable of the jwt.verify() callback function overwrites the res variable of the authenticate middleware, which is why you get this error. You should give it a different name to avoid conflicts.
I think you got the req.cookies from chai-http. If you didn't, you probably forgot to use cookie-parser middleware.
E.g.
index.ts:
import express from 'express';
import cookieParser from 'cookie-parser';
import authenticate from './mws/authenticate';
const server = express();
server.use(cookieParser());
server.get('/user/:id', authenticate, (req, res) => {
const { id } = req.params;
res.json({ id, name: 'teresa teng' });
});
export { server };
authenticate.ts:
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
const authenticate = (req: Request, res: Response, next: NextFunction) => {
const token = req.cookies;
console.log('cookies: ', req.cookies);
if (token) {
jwt.verify(token, process.env.JWT_TOKEN_KEY, (error, verifyResponse) => {
if (error) return res.sendStatus(403);
req.cookies = { _id: verifyResponse._id, locale: verifyResponse.locale };
return next();
});
}
return res.sendStatus(401);
};
export default authenticate;
index.test.ts:
import chai from 'chai';
import chaiHttp from 'chai-http';
import { server } from './';
chai.use(chaiHttp);
const api = chai.request(server).keepOpen();
describe('GET /user/:id', () => {
it('return user information', (done) => {
api
.get('/user/123')
.set('Cookie', '_id=567;locale=en')
.end(function (err, res) {
chai.expect(res).to.have.status(200);
done();
});
});
});
logs:
GET /user/:id
cookies: { _id: '567', locale: 'en' }
1) return user information
I am trying to learn backend and frontend and so far I was okey. But now I have a problem.
In my backend which I use Express, I send cookie with res.cookie as shown below
auth.js (/auth route)
const register = asyncErrorWrapper(async (req, res, next) => {
const {name, email, password} = req.body;
const user = await User.create({
name,
email,
password
});
sendJwtToClient(user, res);
});
tokenHelper.js
const sendJwtToClient = (user, res) => {
// user modeline gore jwt olusturma
const token = user.generateJwtFromUser();
const {JWT_COOKIE, NODE_ENV} = process.env;
return res
.status(200)
.cookie('access_token', token, {
httpOnly:true,
expires: new Date(Date.now() + parseInt(JWT_COOKIE) * 1000 * 60),
secure: NODE_ENV === 'development' ? false : true
})
.json({
success: true,
access_token: token,
data: {
name: user.name,
email: user.email
},
message: 'Your registration is succesful!'
});
};
Then I get response with Angular app in register.component which is connected with Auth Service
from Register.component
registerUser() {
this.sendButton.nativeElement.disabled = true;
this._authService.registerUser(this.user).subscribe(
res => {
if(res.status === 200) {
console.log(res);
}
}, err => {
console.log(err);
this.showError.nativeElement.className += ' alert alert-danger';
this.errMessage = err.error.message;
this.sendButton.nativeElement.disabled = false;
})
}
Auth Service
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class AuthService {
_registerURI = 'http://localhost:3000/auth/register';
_loginURI = 'http://localhost:3000/auth/login';
constructor(private http: HttpClient) { }
registerUser(user) {
return this.http.post<any>(this._registerURI, user, {observe: 'response'});
};
loginUser(user) {
return this.http.post<any>(this._loginURI, user, {observe: 'response'});
};
};
Well I get this response when I click register button: response
Btw I use cors package on backend. Thanks in advance.
I can also share other codes, pages whatever you want. I just thought that this is enough.
Okey, got it.
The problem occurred because of the cors policy. To send cookies to the front-end you need to let in cors package.
Here is the options of cors package that used by express:
app.use(cors( {
origin: 'http://localhost:4200',
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
} ));
And this is the Auth Service method posting registration
registerUser(user) {
return this.http.post<any>(this._registerURI, user, {observe: 'response', withCredentials: true});
};
I'm using nodejs/express and express-jwt module.
this is the server code involved:
async function authenticate({ username, password }) {
const user = await User.findOne({ username });
if (user && bcrypt.compareSync(password, user.hash)) {
if(user.accountActive === true) {
const {hash, ...userWithoutHash} = user.toObject();
const token = jwt.sign({sub: user.id}, config.secret, { expiresIn: '1h' });
console.log(token)
return {
...userWithoutHash, token
};
} else {
throw 'Your account is pending approval.'
}
}
}
function jwt() {
const secret = config.secret;
return expressJwt({ secret, isRevoked }).unless({
path: [
// public routes that don't require authentication
'/users/authenticate',
'/users/register',
]
});
}
i am having {message: "Invalid Token"} message: "Invalid Token" whenever i tried to make a call to any api within the app
Here is the request header: the token is not added to the request headers
i add the authorization header with jwt token if available on the localstorage
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const currentUser = JSON.parse(localStorage.getItem('currentUser'));
if (currentUser && currentUser.token) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${currentUser.token}`
}
});
}
return next.handle(request);
}
when i login the url logs in and sends the token to an authenticate post route
// routes
router.post('/authenticate', authenticate);
module.exports = router;
function authenticate(req, res, next) {
userService.authenticate(req.body)
.then(user => user ? res.json(user) : res.status(400).json({ message: 'username or password is incorrect' }))
.catch(err => next(err));
}
Here is an example of the authenticate login session request after signin
it seems like jwt token even though its valid it becomes invalid after signin.
to me it seems like a token issue, but i don't know where the problem at exactly?
N.B I finally found where the problem was , i did not add the jwt interceptor to my app.module
by adding the bellow code to the providers fixed it for me:
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
This mean that for the token i am providing a value, more than one value (or class) is going to be used.
angular-cli: 8.3.0
node: 10.16
express: 4.17.1
express-jwt: 5.3.1
I need to pass a JWT with GET, POST, PUT, DELETE methods from my UI to my API.
For this I tried to create a front-end using ReactJS. I get:
POST http://localhost:4000/book/ 401 (Unauthorized)
when I try to send a request.
I know I have to pass my token, which I did via postman when I send data to mongo DB.
I'm new to MERN stack, so I am still trying to understand.
This is the book controller
import mongoose from 'mongoose';
import { book_schema } from '../model/book_model';
const Book = new mongoose.model('Book', book_schema);
export const add_new_book = (req, res) => {
let new_book = new Book(req.body);
new_book.save((err, book) => {
if (err) {
res.send(err);
}
res.json(book);
});
};
These are the book routes
import {add_new_book} from '../controller/book_controller';
import {authorization} from '../controller/user_controller';
const book_routes = (app) => {
//GET
app.route('/book')
//POST
.post(authorization,add_new_book);
}
This is the user controller
import mongoose from 'mongoose';
import bycrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { user_schema } from '../model/user_model';
const User = mongoose.model('User', user_schema);
export const register = (req, res) => {
const new_user = new User(req.body);
new_user.hash_password = bycrypt.hashSync(req.body.password, 10);
new_user.save((err, user) => {
if (err) {
return res.status(400).send({
message: err
});
} else {
user.hash_password = undefined;
return res.json(user);
}
});
};
export const authenticate = (req, res) => {
User.findOne({
user_name: req.body.user_name
}, (err, user) => {
if (err) throw err;
if (!user) {
res.status(401).json({ message: 'Authentication Failed ! - No User.' })
} else if (user) {
if (!user.comparePassword(req.body.password, user.hash_password)) {
res.status(401).json({ message: 'Authentication Failed ! - Wrong Password.' })
} else {
var token = res.json({
token: jwt.sign({ user_name: user.user_name }, 'RESTFULAPIs', { expiresIn: '24h' })
});
//$window.sessionStorage.accessToken = response.body.access_token;
return token;
}
}
});
};
export const authorization = (req, res, next) => {
if (req.user) {
next();
} else {
return res.status(401).json({ message: 'Unauthorized User !' })
}
};
export const de_authenticate = (req, res) => {
};
This is the server
import express from 'express';
import cors from 'cors';
import bodyParser from 'body-parser';
import mongoose from 'mongoose';
import jsonwebtoken from 'jsonwebtoken';
import db_config from './config_db.js';
const app = express();
const PORT = 4000;
//import routes here
import book_routes from './api/route/book_route';
import user_routes from './api/route/user_route';
import item_routes from './api/route/item_route';
//mongo DB connection
mongoose.Promise = global.Promise;
mongoose.connect(db_config.DB, { useNewUrlParser: true }).then(
() => { console.log('Database is connected') },
err => { console.log('Can not connect to the database' + err) }
);
app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
//JWT setup
app.use((req, res, next) => {
if (req.headers && req.headers.authorization && req.headers.authorization.split(' ')[0] === 'JWT') {
jsonwebtoken.verify(req.headers.authorization.split(''), [1], 'RESTFULAPIs', (err, decode) => {
if (err) req.user = undefined;
req.user = decode;
//console.log(req.user );
//console.log(decode);
next();
});
} else {
req.user = undefined;
next();
}
});
//to app
book_routes(app);
user_routes(app);
item_routes(app);
app.get('/', (req, res) =>
res.send(`Node Server and Express Server Running on Port : ${PORT}`)
);
app.listen(PORT, function () {
console.log(`Server is running on Port: ${PORT}`);
});
I developed front end using ReactJS and import axios for access API via URL. In insert_book.js file my form submission function is look like this,
onSubmit(e) {
e.preventDefault();
const book_obj = {
book_no:this.unique_id_generator(),
isbn_no: this.state.isbn_no,
author: this.state.author,
published_year: this.state.published_year,
publisher: this.state.publisher,
category:this.state.category
};
axios.post('http://localhost:4000/book/', book_obj)
.then(res => console.log(res.data));
this.setState({
isbn_no: '',
author:'',
published_year: '',
publisher: '',
category:''
})
}
Lastly I would like to provide user routes code,
import {register,
authenticate,
authorization
} from '../controller/user_controller';
const user_routes = (app) => {
//SIGNUP
app.route('/auth/signup')
//POST A USER
.post(register);
//SIGNIN
app.route('/auth/signin')
//AUTHENTICATE USER
.post(authenticate);
}
export default user_routes;
Here are the areas where I am stuck:
How could I store these tokens in sessions to send with future requests?
How to attach and send token value with GET, POST, PUT, DELETE methods?
so to store the token in local storage:
const user: User = getUserFromBackend()
// assuming user is an object
localStorage.setItem('token', user.token);
// get token
const token = localStorage.getItem('token');
to make a request with token header:
const token = localStorage.getItem('token');
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify(data), // body data type must match "Content-Type" header
})
.then(response => response.json())
Although late to answer. I'd suggest storing JWT in cookie if possible. Then you just read the cookie and pass JWT in headers for the request where you need authentication. I prefer to call the header key x-api-token and set its value to the JWT string.
Moreover, you just need to authenticate the user once from your backend by decrypting this JWT and verifying. Once JWT is verified, you can create a session for authenticated user. This session expires in half hour or whatever you've configured in your App.
This way you don't need to send JWT again as the session is valid. I'm not saying that sending JWT is not good and sometimes required in applications where you don't create a session for authenticated user. This happens when performing operations requiring one time authentications like payment processing.
Implementing this logic shouldn't be a problem using node's JWT-decode package (https://www.npmjs.com/package/jwt-decode).
Hope this helps.