Authorizarion on json-server with a JWT - javascript

I am trying to make a node app which is using typicode json-server, I want to add authorization to the app, where GET request is open to all public, but PUT, POST & DELETE request require a JWT token and only then they can proceed on the api.
I have tried to make a small app, but I am not able to figure out the next part of authorization and how to use middlewares on node, as I am a frontend developer.
Here is the app that I have written.
const jsonServer = require('json-server')
const app = jsonServer.create()
const router = jsonServer.router('db.json')
const middlewares = jsonServer.defaults()
const morgan = require('morgan');
const jwt = require('jsonwebtoken');
const config = require('./config');
const bodyParser = require('body-parser');
app.set('Secret', config.secret);
app.use(morgan('dev'));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(middlewares)
app.use((req, res, next) => {
if (req.method === 'GET') {
console.log(req);
next();
} else {
if (req.path === '/login') {
getToken(req, res);
}
if (isAuthorized(req, res)) {
console.log("I am here");
next();
} else {
console.log("I am in error");
res.sendStatus(401)
}
}
})
app.use(router)
app.listen(3000, () => {
console.log('JSON Server is running on 3000')
})
function isAuthorized(req, res) {
console.log("sadasdasdasd");
var token = req.headers['access-token'];
console.log(token);
// decode token
if (token) {
console.log("Inside token");
jwt.verify(token, app.get('Secret'), (err, decoded) => {
console.log("Inside JWT fn");
if (err) {
console.log("Inside JWT fn err");
return res.json({ message: 'invalid token' });
} else {
console.log("Inside JWT fn success");
req.decoded = decoded;
return true;
}
});
} else {
// if there is no token
res.send({
message: 'No token provided.'
});
}
}
function getToken(req, res) {
if (req.body.username === "test") {
if (req.body.password === 123) {
const payload = {
check: true
};
var token = jwt.sign(payload, app.get('Secret'), {
expiresIn: 1440 // expires in 24 hours
});
res.json({
message: 'Authentication Successful ',
token: token
});
} else {
res.json({
error: 'Invalid Password',
});
}
} else {
res.json({
error: 'Please provide valid credentials',
});
}
}

You are doing in right way. But, have some issue in your isAuthorized middleware. In the middleware you have a asynchronous action (jwt.verify), then you can not use this function as a "helper function" as the official document of json-server (the function return boolean value).
Make isAuthorized become a middleware and you it like a middleware:
function isAuthorized(req, res, next) { // Pass 3 parmas to a express middleware
console.log("sadasdasdasd");
var token = req.headers['access-token'];
console.log(token);
// decode token
if (token) {
console.log("Inside token");
jwt.verify(token, app.get('Secret'), (err, decoded) => {
console.log("Inside JWT fn");
if (err) {
console.log("Inside JWT fn err");
return res
.status(401) // I think will be better if you throw http status is 401 to client
.json({ message: 'invalid token' });
} else {
console.log("Inside JWT fn success");
req.decoded = decoded;
return next(); // Only call "next" if everything is good, continue next jobs - handle secured requests
}
});
} else {
// if there is no token
return res
.status(401)
.send({
message: 'No token provided.'
});
}
}
Use the middleware
app.use((req, res, next) => {
if (req.method === 'GET') {
console.log(req);
next();
} else {
if (req.path === '/login') {
getToken(req, res);
}
isAuthorized(req, res, next); // Here
}
})

Related

How to Fix Headers Already Sent to Client in NodeAPI with Angular

I am getting the following error on my node api, which is really just console logging the request at this point.
router.get('/booksByISBN', checkRole, async (req, res) => {
console.log(req.params)
return res.sendStatus(200);
});
node:internal/errors:484
ErrorCaptureStackTrace(err);
^
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
I believe the issue is because of pre-flight CORS data, but no clue how to fix it.
There is one API call in my Angular 15 application, but upon inspection of the Network tab, I see two api calls are actually being made to my endpoint.
I understand this is because of the CORS options request, but I don't know how to fix it to let the API go through.
CheckRole function
var checkRole = async function CheckRoleAuth (req, res, next) {
try {
const token = req.headers.authorization.split(' ')[1];
const decodedToken = jwt.verify(token, envs.jwtSecret);
await User.findById(decodedToken.userId)
.then(foundUser => {
if (foundUser) {
if (foundUser.role != null || foundUser.role != '') {
if (foundUser.role.includes('Admin'))
{
req.userData = {
email: decodedToken.email,
id: decodedToken.id
};
next();
} else {
return res.sendStatus(401);
}
} else {
return res.sendStatus(401);
}
}
})
.catch(err => {
return res.sendStatus(401);
});
} catch (error) {
return res.sendStatus(401);
}
}
You are combining async/await with then/catch in your checkRole middlware, so probably both your checkRole middleware and your endpoint handler try to send back the response.
Refactor your checkRole middleware like this:
const checkRole = async function CheckRoleAuth(req, res, next) {
try {
const token = req.headers.authorization.split(' ')[1];
const decodedToken = jwt.verify(token, envs.jwtSecret);
const user = await User.findById(decodedToken.userId).lean();
if (!user) return res.sendStatus(401);
if (!user?.role?.includes('Admin')) return res.sendStatus(403);
req.userData = { email: decodedToken.email, id: decodedToken.id };
next();
} catch (error) {
return res.sendStatus(401);
}
};

getting Cannot set headers after they are sent to the client error in express js

I have created a middleware that authenticate the user that whether he is verified or not. At the first request when i try to go on protected routes it gives me success messages and verifies the jwt token but when i hit the same request again i get this error.
here is my code !
const Anime = require("../models/admin_model");
const jwt = require("jsonwebtoken");
const checkUserAuthentication = async (req, res, next) => {
const token = req.headers.token;
if (token) {
jwt.verify(token.toString(), "secret", async (err, token_decode) => {
if (err) {
return res.json({
status: 0,
err: err,
msg: "Authorization failed",
});
}
console.log(token_decode);
res.json({ data: token_decode });
next();
return;
});
}
return res.json({
status: 0,
msg: "Authorization failed",
});
};
module.exports = checkUserAuthentication;
res.json({ data: token_decode });
next();
You are responding with JSON, then you are handing control over to the next middleware or route endpoint which (presumably) is making its own response (but it can't because you already responded).
I changed my code to this.
Well maintained if-else statements.
const Anime = require("../models/admin_model");
const jwt = require("jsonwebtoken");
const checkUserAuthentication = async (req, res, next) => {
const token = req.headers.token;
if (token) {
jwt.verify(token.toString(), "thisissecs", async (err, token_decode) => {
if (!err) {
console.log(token_decode);
res.json({ data: token_decode });
next();
} else {
return res.json({
status: 0,
msg: "Authorization failed",
});
}
});
}
else{
return res.json({
status: 0,
msg: "Authorization failed",
});
}
};
module.exports = checkUserAuthentication;

Unauthenticated error in the browser, but it perfectly works on postman

I have this piece of code that works fine on postman when I set the header to the token generated from the access token. but when I'm using the same piece of code on the browser when I log in, I get the authorization access, but the moment I manually input the new router, I get an unauthorized error.
const verifyToken = (req, res, next) => {
const authHeader = req.headers["authorization"];
if (authHeader) {
const token = authHeader.split(" ")[1];
jwt.verify(token, process.env.jwtToken, (err, user) => {
if (err) {
res.status(500).json("Invalid Token");
} else {
req.user = user;
next();
}
});
} else {
res.status(500).json("You are not authenticated");
}
};
const verifyAndAuth = (req, res, next) => {
verifyToken(req, res, () => {
if (req.params.id === req.user.id) {
next();
} else {
res.status(500).json("You are not verified");
}
});
};
the verifyAndAuth middleware is added to every route for example:
router.get("/", admin, async (req, res) => {
try {
const noteData = await users.find();
res.status(200).json(noteData)
} catch (err) {
res.status(500).json(err)
}
});

Error: Route.post() requires a callback function but got a [object Promise]

I am creating a REST API with express, folowing are my architecture,a router is calling a controller.but I got this error, please help me
Error: Route.post() requires a callback function but got a [object Promise]
/////// EmailLogin.js middleware Handler
const { validationResult } = require('express-validator');
let wrapRoute = async (req, res, next) => {
try {
// run controllers logic
await fn(req, res, next)
} catch (e) {
// if an exception is raised, do not send any response
// just continue performing the middleware chain
next(e)
}
}
const EmailLogin = wrapRoute(async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
} else {
var gtoken = req.body.gtoken;
var gSecretKey = env.secret_key;
if (!gtoken) throw new Error('no token')
const captchaURL = `https://www.google.com/recaptcha/api/siteverify?secret=${gSecretKey}&response=${gtoken}`
await axios({
url: captchaURL,
method: 'POST',
headers: {ContentType: 'application/x-www-form-urlencoded'},
}).then(response => {
const gVerifyData = response.data
if (gVerifyData.success === true) {
Users.findOne({'email': req.body.email}).select('+hashPassword +status').exec(function (err, user) {
if(err){
return res.status(500).send({err});
} else if (user) {
validPassword = bcrypt.compareSync(req.body.password, user.hashPassword);
if (!validPassword){
return res.send("wrong-info");
} else if (validPassword && user.status == "active") {
token = jwt.sign({ id: user._id }, env.jwtsecret,
{ expiresIn: "168h" });
res.status(200).send({ token: token, user });
}
} else {
return res.send("wrong-info");
}
}
)
}else {
return res.status(500).send('bot');
}
}).catch(error => {
console.log(error);
});
}
});
function errorHandler (err, req, res, next) {
console.log(err);
// If err has no specified error code, set error code to 'Internal Server Error (500)'
if (!err.statusCode) {
err.statusCode = 500;
}
res.status(err.statusCode).json({
status: false,
error: err.message
});
};
module.exports = {EmailLogin};
I'm trying to call it in my router, like this:
/////// Router.js
const express = require('express');
const router = express.Router();
const { check } = require('express-validator');
const EmailLoginController = require('../controllers/EmailLogin');
var emailLoginValidation = [
check('email').notEmpty().trim().escape().isEmail(),
check('password').notEmpty().isLength({ min: 7 }).withMessage('password is invalid'),
];
router.post('/email-login', emailLoginValidation, EmailLoginController.EmailLogin);
module.exports = router;
/////// App.js
var express = require("express");
var app = express();
const Router = require('./routes/Router');
app.use('/', Router);
app.listen(3000, function() {
console.log('listening on 3000');
});
What could I do ? is it possible to get a Promise Result in the Router as a Handler?
#turkdev Change your email login function to this
const EmailLogin = async (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
} else {
var gtoken = req.body.gtoken;
var gSecretKey = env.secret_key;
if (!gtoken) throw new Error('no token')
const captchaURL = `https://www.google.com/recaptcha/api/siteverify?secret=${gSecretKey}&response=${gtoken}`
await axios({
url: captchaURL,
method: 'POST',
headers: { ContentType: 'application/x-www-form-urlencoded' },
}).then(response => {
const gVerifyData = response.data
if (gVerifyData.success === true) {
Users.findOne({ 'email': req.body.email }).select('+hashPassword +status').exec(function (err, user) {
if (err) {
return res.status(500).send({ err });
} else if (user) {
validPassword = bcrypt.compareSync(req.body.password, user.hashPassword);
if (!validPassword) {
return res.send("wrong-info");
} else if (validPassword && user.status == "active") {
token = jwt.sign({ id: user._id }, env.jwtsecret,
{ expiresIn: "168h" });
res.status(200).send({ token: token, user });
}
} else {
return res.send("wrong-info");
}
}
)
} else {
return res.status(500).send('bot');
}
}).catch(error => {
console.log(error);
});
}
};
The problem was earlier, you were assigning it to method wrapRoute() which returns a Promise, which was not settled, causing the error which you got.
If that was just for calling next() on error, you could always use it in the catch block.

API Authentication with JWT gives WebTokenError always

I've been learning about JWT and I face this problem in which the response gives as JsonWebTokenError. The token generation works fine. But the verification of the token give me an error stating that "JsonWebTokenError" with a message "invalid signature". Here's my code
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
app.get('/api', (request, response) => {
response.json({
message: 'This is an Authentication API'
})
})
app.post('/api/posts', verifyToken, (request, response) => {
jwt.verify(request.token, 'secretkey', (err, authData) => {
if(err){
response.json({err});
}
else{
response.json({
message: 'Post was created successfully',
authData
})
}
})
})
app.post('/api/login', (request, response) => {
const user = {
id: 1,
user: 'sarath',
email: 'sarathsekaran#gmail.com'
}
jwt.sign({user}, 'secretKey', (err, token) => {
response.json({
token
});
});
});
//VerifyToken
//Authori FORMAT: Bearer <token>
function verifyToken(request, response, next){
//Get auth header value
const bearerHeader = request.headers['authorization'];
//Checking if bearer is undefined
if(typeof bearerHeader !== 'undefined'){
//Spilt the token from Bearer
const bearer = bearerHeader.split(' ');
const bearerToken = bearer[1];
//Set the token
request.token = bearerToken;
//Next Middleware
next();
}
else{
//Forbidden
response.sendStatus(403);
}
}
app.listen(5000, ()=>console.log('Server Started'));
While creating a jwt token you should use a unique secret key and should store that unique somewhere else and not directly into the code. You are facing this error because your secret key is having a lowercase of "k" at one place and uppercase at the other.

Categories

Resources