I am new to authentication with node.js and am struggling to implement the following:
I currently have a middleware function that checks the access token sent with the request and pulls the user relating to that token, then appends that user onto the request so I can use their details. This works completely fine for my Users collection, however I am wanting to add a new collection for a completely different type of user, called Owners.
Based on the function I currently have, I cannot seem to find a way to have it check both collections - this is my current function that works with my one Users collection.
//
// Middleware that authenticates token and appends user to request
//
module.exports.required = function (req, res, next) {
const auth_header = req.header("authorization").split(" ");
const auth_type = auth_header[0];
const auth_token = auth_header[1] || null;
// Check token
if (auth_type !== "Bearer" || !auth_token) {
return next(HttpError(401, "Token is invalid."));
}
// Find user matching access token
return User.findOne({ access_token: auth_token })
.orFail(HttpError(401, "Token does not exist."))
.then((user) => {
try {
// Check if token has no expired
decodeToken(auth_token);
} catch (err) {
if (err.name !== "TokenExpiredError") return next(err);
// Refresh token
user.generateAccessToken();
// Save and return new user
return user.save();
}
return user;
})
.then((user) => {
// Append user object to the incoming request
req.user = user;
return next();
})
.catch(next);
};
Can anyone help me understand out how I would check both collections (Users & Owners)?
Related
I have a suspicion about the relative security of some authentication middleware code I came across in a course im enrolled in.
So I used postman to send a request to a protected route(see route code below) and found that I was able retrieve an order for one user with a token generated for another user.
const protected = asyncHandler(async (req, res, next) => {
let token;
if (
req.headers.authorization &&
req.headers.authorization.startsWith("Bearer")
) {
try {
token = req.headers.authorization.split(" ")[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = await User.findById(decoded.id).select("-password");
next();
} catch (error) {
console.error(error);
res.status(401);
throw new Error("Not authorized, token failed");
}
}
if (!token) {
res.status(401);
throw new Error("Not authorized, No token found");
}
});
export protected
It seems evident to me that this middleware code will only verify if a user from decoded token exists in the DB and but will not limit access to resources based on the user/token.
import {addOrderItems, getOrderbyId} from "../controllers/orderController.js";
import { protected } from "../middleware/authMiddleware.js";
const router = express.Router();
router.route("/").post(protected, addOrderItems);
router.route("/:id").get(protected, getOrderbyId);
//:id is the order id
However, when testing another protected route for updating a user's profile info, I receive an error when using wrong token.
Was hoping for some clarification
jwt.verify will only verify that the given token is generated by the server or not. It doesn't care which user send this token.
For your protected middleware, it just check if the request is authorized. If so, the request will pass to the controller.
As for the updating route. It probably be something like this:
// route
router.route("/:userId", protected, updateController)
const updateController = (req, res) => {
const user = req.user; // this is the one generated by protected middleware
const reqUserId = req.params.userId; // this is the one send by request
if (user.id !== reqUserId) {
// if two ids are not the same, it means someone is trying
// to update the profile with the wrong token
res.status(401);
}
// update profile in database
}
I'm fairly new to Express and NodeJS. I'm having trouble accessing my custom created header named auth-token when trying to verify the existing of said user first before allowing them to do any CRUD functionality in the system. It just returned 'undefined' instead of the token I placed in it.
So below is where I created my custom header named auth-token in my home GET router.
// Home GET Router
router.get('/', verifyUser, async (req, res) => {
// get user data by id
const dbData = await All.findById({ _id: req.user._id })
// store token passed as query 'tkn' in 'token' var
const token = req.query.tkn
// create custom header & render 'index.ejs' or homepage
res
.header('auth-token', token)
.render('index', { data: dbData.data })
}
I successfully able to create the custom header auth-token with no problem as shown below in my index or home page:
Right now, I'm trying to save new data inserted by user in the home page by using Home POST Router as shown below. But it will check first whether the user has the token or not using verifyUser1st function:
// Home POST Router
router.post('/', verifyUser1st, async (req, res) => {
// save new data code here...
}
And this is my verifyUser1st function:
function verifyUser1st(req, res, next) {
// get token from header
const token = req.header('auth-token') // this will return undefined
// if have, then allow/continue next(). If don't have, then return error message
if(!token) return res.status(401).json({ message: 'Accessed Denied!' }) // I got this error since token = undefined
try {
// verify the exist token
const varified = jwt.verify(token, process.env.TOKEN_4LOGINUSER)
req.user = varified
next()
} catch(err) {
res.status(400).json({ message: 'Invalid token!' })
}
}
But unfortunately it returns Accessed Denied since the token is undefined.
Should the auth-token be in Request Headers section (in blue circle image above) instead of Response Header section (in red circle image above) in order for it to work?
If yes, then how can I do that? If not, then can you help enlighten me of what things or topics should I learn first in order for me to make this work since I'm kinda new to this HTTP, Express and NodeJS environment?
to answer your question briefly - if you want to pass the auth token in the header then it should be passed in the request header.
However, if you want some middleware to check a token value that you can use later on in the processing chain, then just set it as a custom property on the req object and access it from there. There is no reason to try to jam something into the headers and then parse it out again later.
const auth = (req, res, next) => {
let { token } = req.body;
try {
console.log(token)
if (!token)
return res.status(401).json({ msg: 'no authentication token, authorisation denied' })
const verified = jwt.verify(token, process.env.JWT_KEY);
if (!verified)
return res.status(401).json({ msg: 'no authentication token, authorisation denied' })
req.user = verified.id;
next();
}
catch (err) {
res.status(500).json({ error: err.message });
}
}
So instead of sending the token in a Header we can either grab it from somewhere in the state, or in your case from the local storage.
In the front end we would do something like const token = localStorage.getItem("auth-token");
And then we would pass this token to the API request.
I am pretty new to loopback and here is what I am doing:
I am using standard login route provided by the loopback to log in the users - extended base Users to my own model say orgadmin.
With prebuilt route /api/orgadmin/login, I can easily login.
Now, I have a flag in orgadmins say 'status' which can be either 'active' or 'inactive' based on which I have to defer user login.
I was thinking something with remote hooks like beforeRemote as below but it doesn't work:
//this file is in the boot directory
module.exports = function(orgadmin) {
orgadmin.beforeRemote('login', function(context, user, next) {
console.log(user)
// context.args.data.date = Date.now();
// context.args.data.publisherId = context.req.accessToken.userId;
next();
});
};
So what is the best way to accomplish this?
The user attribute will only be available if the request is coming with a valid access token. The attribute is unused for unauthenticated requests, which login is.
Here's a possible alternative:
module.exports = (OrgAdmin) => {
OrgAdmin.on('dataSourceAttached', () => {
const { login } = OrgAdmin;
OrgAdmin.login = async (credentials, include) => {
const accessToken = await login.call(OrgAdmin, credentials, include);
const orgAdmin = await OrgAdmin.findById(accessToken.userId);
if (orgAdmin.status !== 'active') {
OrgAdmin.logout(accessToken);
const err = new Error('Your account has not been activated');
err.code = 'NOT_ACTIVE_USER';
err.statusCode = 403;
throw err
}
return accessToken;
};
});
};
The above code overrides the login method and does the following:
Login the user, using loopback's built-in login
Take the response of login, which is an access token, and use it to get the user.
If the user is active, return the access token, satisfying the expected successful response of login.
If the user is not active, remove the access token that was created (which is what logout does), and throw an error.
I am using sails-generate-auth in my sails.js app. I followed this tutorial to integrate this with my app. When I call localhost:1337/auth/local/register it routes to my callback action in AuthController. My callback action is as follows
callback: function (req, res) {
function tryAgain(err) {
//some validation
}
}
passport.callback(req, res, function (err, user, challenges, statuses) {
if (err || !user) {
return tryAgain(challenges);
}
req.login(user, function (err) {
if (err) {
return tryAgain(err);
}
//Return the access token created by passport instead of success.
res.send("Success");
});
});
I want to replace res.send("Success"); with the access token created by passport. But User.passport seems to be null at this point. How do I get the user's access token at this point?
The User You Get only contains the data from the 'User Collection' which contains the username email and id. The 'Passport' collection is a seperate collection which contains hashed password, ID, userID(which is equal to the ID In the 'User' Collection) and a token. You need to search in the 'passport' collection for the relavent User. Here is the algoritem:
passport.callback(req, res, function (err, user, challenges, statuses) {
if (err || !user) {
console.log(err);
return tryAgain(challenges);
}
req.login(user, function (err) {
if (err) {
console.log(err);
return tryAgain(err);
}
// Mark the session as authenticated to work with default Sails sessionAuth.js policy
req.session.authenticated = true
console.log(user);
var userID = user.id;
Passport.find({user: userID}, function(err, items){
if(err) return err;
console.log(items[0].accessToken);
// Make sure you dont give them any sensetive data
res.json({userData: user, token: items[0].accessToken});
});
// Upon successful login, send the user to the homepage were req.user
//res.redirect('/');
});
});
If you want to use sails built-in sessions, you do not need to send any token to the client, everything is stored server-side in the sessions and it is tied to the user by the sid (session id) cookie, therefore you can redirect to any page.
As long as you have your sessionAuth policy it will check that the user is logged-in before accessing your protected routes.
If you would like to use something like Json Web Tokens (JWT) though, sails-generate-auth / sails-auth do not support it yet
I have a Node.js app built with Express.js framework.
I want to check that the user is authorized to do a certain request, I do this by requiring the clients to supply an access token in a header.
I don't want to add this to each of the individual functions that the clients have access to. Like this, for an info request about a user:
exports.info = function(req, res) {
var userId = req.params.id,
accessToken = req.headers["accesstoken"];
console.log("received request to get info for userID <"+ userId +">");
users.User.findOne({accessToken: accessToken}, function(err, user) {
if(user == null) {
...
How can I do this at a higher level? Can I set this header requirement somewhere on a global for express?
I want to do this basically for all functions except for the user login function, so all functions except for one.
You can make a small middleware:
verifyUser = function(req,res,next){
var userId = req.params.id, accessToken = req.headers["accesstoken"];
console.log("received request to get info for userID <"+ userId +">");
users.User.findOne({accessToken: accessToken}, function(err, user) {
if(user == null) {
...
}
next()
}
}
Then:
On one request:
app.get("/user/info", verifyUser, exports.info)
On a selection of requests:
app.all(SomeRegex, verifyUser)
On all resquests:
app.use(verifyUser)
You can create a middleware and set it up on each route, you need to authorize. Example:
var myAuthMiddleware = function (req, res, next) {
// Here goes your code to check if the user complies
// with the conditions. You can use req.headers, req.user, etc
if (conditionIsMet) return next(); // If the user complies, you continue the process
// The user doesn't comply
return res.send('Error');
}
Then, you use his middleware in the needed routes:
app.get('/my-route', myAuthMiddleware, myRouteHandler);
app.post('/another-route', myAuthMiddleware, myOtherRouteHandler);
// This one doesn't need auth
app.get('/', indexHandler);
Just add your function as one more of the express middleware that runs before all your request processing.
app.use(function(req, res, next) {
var userId = req.params.id,
accessToken = req.headers["accesstoken"];
console.log("received request to get info for userID <"+ userId +">");
users.User.findOne({accessToken: accessToken}, function(err, user) {
if(user != null) {
return next(); // This is ok, keep processing
} else {
// don't call next, redirect to login page, etc...
}
}
app.get('/home', ...);
apg.get('/some_other_page');
You call next to get express to process as usual, or you use redirect, or return an error and don't call next.