I am trying to build a simple web token protected api in nodejs. I have been following this tutorial authenticate a node js api with json web tokens and have been implementing the steps in my app. I now have an api running that allows me to get/post/put/delete and a route that generates a webtoken for the user and shows it in plain text (for dev purposes). I am using node-restful for the api's but I am having some trouble understanding how I would actually verify if the client is sending the webtoken in their request, before allowing these get/post/put/delete requests.
Here is my router. Where I define the allowed requests:
const express = require('express')
const router = express.Router()
// Models - Load models here
var userModel = require('./models/User')
// Controllers - Load controllers here
const userController = require('./controllers/userController')
// Routes - Define routes here
router.post('api/authenticate', userController.authenticate) //Route that generates the webkey and shows it in the response
// Configure the endpoint that node-restful will expose. Here I want to first check if the user is sending his or her api key. Before allowing these methods.
userModel.methods(['get', 'put', 'post', 'delete'])
userModel.register(router, '/api/users')
// Export the router object
module.exports = router
Here is my userController where the token is generated.
// Dependencies
const User = require('../models/User')
const jwt = require('jsonwebtoken')
const config = require('../config.js')
module.exports = {
authenticate: function(req, res, next) {
// find the user
User.findOne({username: req.body.name}, function(err, user) {
if (err) throw err;
if (!user) {
res.json({
success: false,
message: 'Authentication failed. User not found.' });
} else if (user) {
// check if password matches
if (user.password != req.body.password) {
res.json({
success: false,
message: 'Authentication failed. Wrong password.' });
} else {
// if user is found and password is right
// create a token
var token = jwt.sign(user, config.secret, {
expiresIn: 60*60*24 // expires in 24 hours
});
// return the information including token as JSON
res.json({
success: true,
message: 'Enjoy your token!',
token: token
});
}
}
})
}
}
And here is my user model.
// Dependencies
const restful = require('node-restful')
const mongoose = restful.mongoose
// Schema
const userSchema = new mongoose.Schema({
username: String,
password: String,
email: String
})
// Return the model as a restful model to allow it being used as a route.
module.exports = restful.model('User', userSchema)
Is there some way I can protect these endpoints, using the same manner of syntax as I am currently using to expose them? I believe I would have to check for the web token before defining the methods:
userModel.methods(['get', 'put', 'post', 'delete'])
userModel.register(router, '/api/users')
If I simply remove the methods themselves, the user will not be able to get the page and is shown a: "Cannot GET /api/users" error. What if I wanted to show a custom error? For example: "No web token provided. Register to authenticate" etc etc? Any help is much appreciated. Thank you in advance.
I now have a function that checks for the token before serving a page. It seems to work for now. Currently I am passing the token manually in postman as a header: x-access-token. How would I catch the token upon generation and automaticly make the client send it on future requests? Here is the function that checks for the token and the protected route.
Great. I kept working while waiting for any answers and completed this step. I can now generate the token and using postman pass that to a secured route I created. It works perfectly, but I am struggeling to understand how I am going to save the token on the client side and pass that on every request. I still generate the token, the same way as above. I can verify the token by manually passing it in my header as x-access-token, but how would I do this automaticly?
Update
Here is the function that checks the token and a protected route that utilizes that function:
// Routes - Define routes here
function getToken(req, res, next) {
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, config.secret, 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;
console.log(decoded);
next();
}
});
} else {
// if there is no token
// return an error
return res.status(403).send({
success: false,
message: 'No token provided.'
});
}
}
router.get('/entries', getToken, entryController.get)
I found this question save-token-in-local-storage-using-node Which solved the last piece of the puzzle.
You can simply write a middleware for this kind of purpose. Clients will generally send tokens in header, so that you can get the header information and verify it. Your middleware will be something like this.
module.exports = (req, res, next) => {
if (!req.headers.authorization) {
return res.status(401).json({
success: false,
message: "You are not authorized for this operation."
})
}
// get the authorization header string
const token = req.headers.authorization
// decode the token using a secret key-phrase
return jwt.verify(token, config.secret, (err, decoded) => {
// the 401 code is for unauthorized status
if (err) {
return res.status(401).json({
success: false,
message: "You are not authorized for this operation."
})
}
const username = decoded.username
// check if a user exists
return User.findOne({username: username}, (userErr, user) => {
if (userErr) {
return res.status(500).json({
success: false,
message: "Error occured while processing. Please try again.",
err: userErr
})
}
if ( !user ) {
return res.status(401).json({
success: false,
message: "You are not authorized for this operation."
})
}
return next()
})
})
}
For the security reasons it is better to store JWTs in your application associated with the user. Complete explanation can be found here.
Update:
You can save the token in cookie and parse the cookie to find out the token and then verify that.
Related
I want to destroy the JWT whenever user sends the logout request to the app.
First of all, I am not storing JWT in the database so I can not delete that and I am also not using cookies or sessions. Is JWT stored on the client side? If so how can I destroy the JWT and invalidate the user's requests after logging out of the app.
The token middleware:
module.exports = middlewares = {
authenticateToken: async (req, res, next) => {
try {
if (!req.headers["x-access-token"]) {
return res.status(401).json({
error: "Key x-access-token not found",
});
}
if (req.headers["x-access-token"] === "") {
return res.status(401).json({
error: "Token not found",
});
}
const token = req.headers["x-access-token"];
const data = jwt.verify(token, keys.JWToken);
if (!data) return res.status(401).json({ error: "Invalid token" });
req.data = data;
next();
} catch (err) {
return res.status(400).json({ error: err.message });
}
},
};
Here's how I generate token on the registration and login requests:
const payload = { id: new_user._id };
const JWToken = jwt.sign(payload, keys.JWToken, { expiresIn: 31556926 });
The code provided is from the server, hence I don't know how its being saved on the client side, usually it is done using localhost, cookie or session. But you have mentioned that you are not using cookie or session, hence there is a chance that you are using local storage to store the jwt token. You can check your local storage on chrome by going to developer options -> Application -> Local Storage. You may find your token by how you named it, you can access it and delete by localStorage.removeItem("name of your token");
I am using JWT to set a token for user authentication on specific routes. The authentication works perfectly with the Postman routes, but when I log in using the browser and the app sends the GET requests to my Heroku backend, I get a 401 Unauthorized error. I am using the same Heroku URL when sending a postman request, so I am sure that my Heroku hosting is not the issue.
The flow of the app is as follows:
Once a user tries to log in, we check credentials with the DB and if the credentials are correct, we then assign a jwt token to the cookie. This occurs on the server side using the encode function below.
export const encode = async (req, res, next) => {
try {
const { username, password } = req.params;
const verifiedLogin = await User.onUserLogin(username, password);
if (verifiedLogin.success === false) {
return res.status(401).json({ success: false, message: "Invalid login credentials" });
}
const token = jwt.sign({ userid: verifiedLogin.user._id, userType: verifiedLogin.user.type }, SECRET_KEY);
res.cookie("Authorization", token, { httpOnly: false, secure: false });
console.log("cookie", res.cookie);
return res.status(200).json({ success: true, userId: verifiedLogin.user._id });
} catch (error) {
return res.status(400).json({
success: false,
message: "Problem while trying to authenticate ",
error: error,
});
}
};
Once the user is logged in, we then run a series of GET requests from the client side to retrieve some data from our DB. None of the GET requests are authorized after login so I will only provide one of them here.
export const fetchUserGroups = async () => {
let roomsFromResponse = [];
// Request to get the groups the user is part of for the groups panel
await axios
.get(`http://saldanaj97-chattyio.herokuapp.com/room/user-messages/`, CONFIG)
.then((response) => {
response.data.roomIds.map((room) => {
const newRoom = { id: room._id, groupName: room.groupName, lastMessageReceived: { user: "", contents: "" } };
return (roomsFromResponse = [newRoom, ...roomsFromResponse]);
});
})
.catch((error) => {
console.log("Auth error when retrieving users groups", error);
});
return roomsFromResponse;
};
Lastly, when a user is trying to access a route that requires authentication(such as the one above), the decode middleware below is used to decode the jwt token.
export const decode = (req, res, next) => {
if (req.cookies === "") {
return res.status(400).json({ success: false, error: "No access token provided " });
}
const accessToken = req.cookies["Authorization"];
try {
const decoded = jwt.verify(accessToken, SECRET_KEY);
req.userId = decoded.userid;
req.userType = decoded.userType;
return next();
} catch (error) {
return res.status(401).json({
success: false,
message: "Could not decode authorization token",
});
}
};
Below are my cors settings from the backend.
// Cors
const corsOptions = {
origin: ["http://localhost:3000"],
exposedHeaders: ["Authorization"],
};
app.use(cors(corsOptions));
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content, Accept, Content-Type, Authorization");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
res.setHeader("Access-Control-Allow-Credentials", true);
next();
});
When a user logs in, an error is caught and the error is posted in the console with a 401 code along with the 'could not decode authorization token' from the decode function catch block from above. When I go to check the application settings in chrome, there is no Authorization token being set after login, but when I run the same GET request in postman, the cookies ARE being set.
I have tried different things such as settings 'httpOnly' to true as well as setting 'secure' to true but neither has worked. This is my first app that I have tried hosting and every feature was working fine while developing in localhost but once I put it on Heroku I have not been able to get the authorization to work in the browser even though all my requests from postman continue working.
I am not sure if this is a cors issue, an issue with the decode function, or a different issue altogether. Any help would be appreciated.
If you're not on the same domain, you can't set cookie for the client from server, it works in postman since you're hitting the endpoint from the same domain.
https://stackoverflow.com/a/44516536/12490386
I have an iOS app and nodeJS backend. Currently I have implemented passport-facebook strategy. From the app I get the facebook token, and I send it to backend where I authorise the user.
// config
var FacebookTokenStrategy = require('passport-facebook-token');
const passport = require('passport')
const { facebook_client_id, facebook_client_secret } = require('../config')
passport.use(new FacebookTokenStrategy({
clientID: facebook_client_id,
clientSecret: facebook_client_secret,
}, function (accessToken, refreshToken, profile, done) {
done(null, profile)
}
));
And the middleware
const passport = require('passport')
require('../config/passport-facebook')
require('../config/passport-apple')
require('../config/passport')
const { INVALID_TOKEN, UNAUTHORIZED } = require('../config/constants')
module.exports = (req, res, next) => {
passport.authenticate(['apple','facebook-token', 'jwt'], function (err, user, info) {
if (err) {
if (err.oauthError) {
res
.status(400)
.json({ message: INVALID_TOKEN })
}
} else if (!user) {
res
.status(401)
.json({ message: UNAUTHORIZED })
} else {
req.user = user
next()
}
})(req, res, next);
}
Now I need to implement apple login. I tried using this library passport-apple
But I can not make it work. I am receiving the token from the app, send it to the back, but I only get
GET - /api/v1/shirts/?sorted%5BcreatedAt%5D=-1&filtered%5Bstate%5D=&pageNum=1&pageSize=10 - 302 - Found - 0b sent - 15 ms
I don't know if this is the correct approach. Should I get the user info from the app, send it to the backend and assign a JWT token to the created user? Or how can I do the same as I did with facebook?
After several try I find the solution thanks to this documentation https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens
You need to send that in your body POST:
{
"grant_type": "authorization_code",
"code": "YOUR_CODE",
}
code:
"The authorization code received in an authorization response sent to your app. The code is single-use only and valid for five minutes. This parameter is required for authorization code validation requests." Apple Documentation
before i started working with reactJS i was using express sessions (with expressJS of course) to determine whether user was authenticated or not, my middleware was passed in /profile URL like this router.use('/profile', middleware, require('./profilePageFile')) and if user was not authenticated i was redirecting to login page with simple code
if(!req.session.user){
res.redirect('/login')
}
i tried to use redirecting with react too but since react has it's own routing system (react-router-dom) and express is only needed for creating APIs when i was logging in /profile url it was still showing me page content and redirecting me after xxx milliseconds later, and i think it would be better practice if i have my profile page and main page on default url ( 'domain.com/' ), as i see many websites are using this technique including Facebook, at this point i was trying to make something like this: if user has not token or token expired, don't display some "hello user" button, otherwise display it. my only problem is that i do not know how to do that.
if i have boolean in my react state called isAuthenticated or something like this which determines whether user is authenticated or not according to the header that i send from server-side, it would be bad practice for security, i think, and also when i tried that, it did not work anyway. at this point only thing that i can do is to pass req.userId to client if token exists. this works but it is not enough, if anyone got the point i will be glad if i get help
here is my middleware code
const guard = (req, res, next) => {
const token =
req.body.token ||
req.query.token ||
req.headers["x-access-token"] ||
req.cookies.token;
if (!token) {
res.status(401).send({ auth: false });
} else {
jwt.verify(token, process.env.SECRET, function(err, decoded) {
if (err) {
return res.status(500).send({
message: err.message
});
}
req.userId = decoded.id;
res.status(200).send({ auth: true });
next();
});
}
};
I have made two changes to your code.
const guard = (req, res, next) => {
const token = req.body.token ||
req.query.token ||
req.headers['x-access-token'] ||
req.cookies.token;
if (!token) {
// Authentication failed: Token missing
return res.status(401).send({ auth: false })
}
jwt.verify(token, process.env.SECRET, function (err, decoded) {
if (err) {
// Authentication failed: Token invalid
return res.status(401).send({
auth: false,
message: err.message
})
}
req.userId = decoded.id
next()
})
}
First, inside the if(err) condition I have changed the status code to 401 because if the token is invalid, it will raise the error here.
Secondly, I have removed the res.status(200).send({auth:true}) from the bottom of the function.
This is because the middleware should pass on to the route (which we are trying to protect with the JWT check) to respond. This was responding to the request before it got to the actual route.
Is there any way by which, I could segregate my multi Api(S) like
employees Api and its page redirection
customer Api and its page redirection etc !
Since currently what, I am doing is
var employees = require('./routes/employees');
var customers = require('./routes/customers');
app.use('/customers', isAuth, customers);
app.use('/employees', isAuth, employees);
My JWT Part look like this
function isAuth(req, res, next) {
var token = req.body.token || req.param('token') || req.headers['x-access-token'];
if (token != undefined) {
// 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.'
})
}
} else {
//check whether request is api or normal request !
var string = req.url,
substring = "api";
if (string.indexOf(substring) !== -1) {
res.status(403).send({
success: false,
message: 'No token provided.'
})
} else {
//check whether request is authenticated or not !
if (req.isAuthenticated())
return next();
/* res.status(401).json({authenticated: false});*/
res.redirect('/login');
}
}
}
Any recommendations, improvements how to improve on below pointers with reference from above code snippets
separate routes of page redirection and apis much like in
phoenixframework of elixir.
jwt api routes authentications without current overcomplications of
aforementioned code snippets !.
here is code snippets of my elixir running under phoenix framework with simple api and page level segregation without jwt !
defmodule HelloPhoenix.Router do
use HelloPhoenix.Web, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", HelloPhoenix do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
end
# Other scopes may use custom stacks.
scope "/api", HelloPhoenix do
pipe_through :api
# resources "/users", UserController, except: [:new, :edit]
resources "/users", UserController do
post "/filterbackend", UserController,:filterbackend
post "/bulkcreate", UserController,:bulkcreate
post "/testpost", UserController,:testpost
post "/filterRecordset", UserController,:filterRecordset
end
end
end