I've a code like this on my server.js
var session = {
v1: require('./routes/v1/session')
}
app.post('/v1/session/login', session.v1.login);
app.get('/v1/session/logout/:uuid', session.v1.logout);
var modify = {
v1: require('./routes/v1/modify')
}
app.put('/v1/modify/:uuid/password', modify.v1.password);
app.put('/v1/modify/:uuid/mobile', modify.v1.mobile);
app.put('/v1/modify/:uuid/email', modify.v1.email);
All the cases above I am sending an Authorization header with a token, except for Login because you will receive a token after the login.
So, in all of my routes, I need to call a routine to validate the token and then perform the operations. Like this:
exports.logout = function (req, res) {
var auth = req.headers.authorization;
global.utils.validateToken(auth).then(function(uuid) {
// Code here
}
}
But I am pretty convinced that there is a better way to do that. Something like execute an authorization check for each server request before call the router.
Is such thing possible ? And if yes: Can I define exceptions (for example...in case of Login, I don't need to check the Authorization) ?
thanks !
There are multiple ways to do so, I am stating three approaches, you can go with whichever you like.
So basically, you need to make a middleware which should not be called while login.
what you will do is, make a middleware as given below:
var middleware = {
authTokenValidator = function(req, res, next) {
var auth = req.headers.authorization;
global.utils.validateToken(auth).then(function(uuid) {
// Token Valid Code here
next();
}).catch(function(err) {
// Token failure handling code here
res.status(401).json(err);
})
}
}
now you can use this middleware two ways:
First is using middleware for the routes wherever needed.. as written below:
var session = {
v1: require('./routes/v1/session')
}
// Skipped for login
app.post('/v1/session/login', session.v1.login);
app.get('/v1/session/logout/:uuid', middleware.authTokenValidator, session.v1.logout);
var modify = {
v1: require('./routes/v1/modify')
}
app.put('/v1/modify/:uuid/password', middleware.authTokenValidator, modify.v1.password);
app.put('/v1/modify/:uuid/mobile', middleware.authTokenValidator, modify.v1.mobile);
app.put('/v1/modify/:uuid/email', middleware.authTokenValidator, modify.v1.email);
so basically using next function you can pass any number of functions as middleware and call next middleware function.
The second way is passing middleware globally and handle exceptions like login URL:
define all the routes above middleware which should not go through middleware and others below middleware as below code for reference:
var session = {
v1: require('./routes/v1/session')
}
// Skipped for login
app.post('/v1/session/login', session.v1.login);
// All the requests except the above path will go throgh this middleware
app.use(middleware.authTokenValidator)
app.get('/v1/session/logout/:uuid', session.v1.logout);
var modify = {
v1: require('./routes/v1/modify')
}
app.put('/v1/modify/:uuid/password', modify.v1.password);
app.put('/v1/modify/:uuid/mobile', modify.v1.mobile);
app.put('/v1/modify/:uuid/email', modify.v1.email);
and, the Third approach is similar to the Second one but checking request path in middleware and skip it, which is not preferable...
var middleware = {
authTokenValidator = function(req, res, next) {
var no_validate_path = ['/v1/session/login']
// Skipping the login path here
if (no_validate_path.indexOf(req.path) >= 0) {
next()
} else {
var auth = req.headers.authorization;
global.utils.validateToken(auth).then(function(uuid) {
// Token Valid Code here
next();
}).catch(function(err) {
// Token failure handling code here
res.status(401).json(err);
})
}
}
}
And your route code as:
//Adding middleware for all the paths
app.use(middleware.authTokenValidator)
var session = {
v1: require('./routes/v1/session')
}
app.post('/v1/session/login', session.v1.login);
app.get('/v1/session/logout/:uuid', session.v1.logout);
var modify = {
v1: require('./routes/v1/modify')
}
app.put('/v1/modify/:uuid/password', modify.v1.password);
app.put('/v1/modify/:uuid/mobile', modify.v1.mobile);
app.put('/v1/modify/:uuid/email', modify.v1.email);
References to read about middlewares https://expressjs.com/en/guide/using-middleware.html
Related
I have a controller that receives an user that is trying to login via form. When all validations are checked, the user will be logged in and a token will be created in the following way:
const token = jwt.sign({userId: user._id}, config.secret ,{expiresIn: '24h'})
res.json({success: true, message: 'SesiĆ³n iniciada', token: token, user: {email: user.email}})
However, how do I access this token from another controller? I've seen that a good approach would be to create a middleware that intercepts such token, but I don't really know how to accomplish this.
I'd be happy only knowing how to get the token tho. I'm kinda new and I'm taking very small steps.
You should setup your client requests to send such token as #Vahid said.
Here's an example with axios
const instance = axios.create({
baseURL: 'https://some-domain.com/api',
// From the docs:
// `transformRequest` allows changes to the request data before it is sent to the server
// This is only applicable for request methods 'PUT', 'POST', 'PATCH' and 'DELETE'
// The last function in the array must return a string or an instance of Buffer, ArrayBuffer,
// FormData or Stream
// You may modify the headers object.
transformRequest: [function (data, headers) {
headers['Authorization'] = localStorage.getItem('jwt')
return data;
}],
})
export default instance
In case you also need GET request you can add:
export setAuthToken = (token) => {
instance.defaults.headers.common['Authorization'] = token;
}
Although you'll need to call it every time your JWT is renewed.
After that, you could catch it using the Middlewares to decode the token from the headers
app.use((req, res, next) => {
const authToken = req.headers['Authorization']
if(authToken) {
try {
const decoded = jwt.verify(authToken, config.secret)
req.user = decoded.userId
// Hopefully
// req.user = getUserById(decoded.userId)
next()
} catch(e) {
// Handle Errors or renewals
req.user = null
// You could either next() to continue or use 'res' to respond something
}
} else {
// Throw 403 if should be authorized
res.sendStatus(403)
}
})
This way you should be able to access req.user on any route defined after your middleware.
Eg:
app.post('/me', (req, res) => {
res.send(req.user)
})
Note that this is just one example of a global middleware. In other cases, you should be able to create custom middlewares based on which routes you want to protect or with which amount of permissions.
I've got a Node app using express-openapi-validator that takes a an api spec file (which is a .yml file), with request and response validation. The express-openapi-validator package routes the request to a handler file (defined in the spec). This is what one of the handlers might look like:
function getUsers(req, res) {
const { 'x-user-id': userId } = req.headers
res.status(200).json(`Your userId is ${userId}`)
}
I've got an API key feature, where users can get a new API key, and the other endpoints that need the caller to have the API key in the request headers to validate the request.
I know it should be possible to use middleware to validate the request, but I can't figure out how to use custom middleware with the express-openapi-validator package on select endpoints.
For eg:
GET /apikey = does not require api key
GET /resource = requires api key
How do I configure this?
Here's what the openapi validator code in my app.js looks like:
new OpenApiValidator({
apiSpec,
validateResponses: true,
operationHandlers: path.join(__dirname, './handlers'),
})
.install(app)
.then(() => {
app.use((err, _, res) => {
res.status(err.status || 500).json({
message: err.message,
errors: err.errors,
});
});
});
I actually ended up finding a solution for this myself.
First of all, I'm using version 4.10.5 of express-openapi-validator, so the code above is slightly different.
Here's what it looks like now:
// index.js
app.use(
OpenApiValidator.middleware({
apiSpec,
validateResponses: true,
operationHandlers: path.join(__dirname, './handlers'),
validateSecurity: {
handlers: {
verifyApiKey(req, scopes) {
return middleware.verifyApiKey(req)
},
bearerAuth(req, scopes) {
return middleware.verifyToken(req)
}
}
},
}),
);
app.use((err, req, res, next) => {
res.status(err.status || 500).json({
message: err.message,
errors: err.errors,
});
The way I ended up using middleware in my routes is below:
I've added a securitySchemes section in my swagger.yml file, like so:
components:
securitySchemes:
verifyApiKey:
type: apiKey
in: header
name: x-api-key
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
There's a bit more information about it here: https://swagger.io/docs/specification/authentication/
On each route that needs the middleware, I'm adding a security section, like so:
/team:
post:
security:
- bearerAuth: []
description: Create a new team
operationId: createTeam
x-eov-operation-id: createTeam
x-eov-operation-handler: team
As you can see in my code above (in the index.js file), I've got a validateSecurity key, with a handlers key that then has the correlating keys that are in my swagger.yml (verifyApiKey and bearerAuth). These functions get the request and scope to check if they're valid. These functions return a boolean value, so true means that the middleware lets the request through, and false means a 403 response will be returned.
validateSecurity: {
handlers: {
verifyApiKey(req, scopes) {
return middleware.verifyApiKey(req)
},
bearerAuth(req, scopes) {
return middleware.verifyToken(req)
}
}
},
Please respond if I've got anything above wrong, or if the explanation can be clearer. If you have questions, please post them below.
You can simply pass array of handlers instead of just 1 function, like in express.
So in you code, the getUsers function that probably is what the x-eov-operation-id refers to, would be an array of 2 functions:
const getUsers = [
apiKeyMiddleware,
(req, res) => {
const { 'x-user-id': userId } = req.headers
res.status(200).json(`Your userId is ${userId}`)
}
];
I was in a similar situation as you, using OpenAPI/Swagger packages like that limited my ability to add specific middleware per endpoint, so my solution was I created an npm module called #zishone/chaindler.
You can use it like this:
const { Chain } = require('#zishone/chaindler');
function getUsers(req, res) {
const { 'x-user-id': userId } = req.headers
res.status(200).json(`Your userId is ${userId}`)
}
function postUsers(req, res) {
// ...
}
function mw1(req, res, next) {
next()
}
function mw2(req, res, next) {
next()
}
module.exports = {
getUsers: new Chain(mw1, mw2).handle(getUsers),
postUsers: new Chain(mw1).handle(postUsers)
}
Basically it just chains the middlewares then calls them one by one then call the handler/controller last.
I am trying to build a token system to allow authentication via an email link. The flow I am thinking might work is...
click email link (of the form site.com/login?token=hf74hf64&email=m#email.com) -> server checks the token is valid and the email is registered -> server redirects to '/' with a session cookie -> client acknowledges session cookie and authenticates the user
The last step is where I'm having trouble. How do I detect from within my component that a session cookie is present?
I was thinking of something like this in my React auth component:
class AuthenticatedComponent extends Component {
componentWillMount() {
if (cookie) {
this.props.dispatch(authenticateUser())//.....
}
}
}
Might this work, or do I need to make a separate fetch to the server and trigger the dispatch depending on the response?
We've implemented a very similar approach for our app. For this to work, we handle all the login in Node and not in the actual components.
Check if token is provided in query string
Pass token to server to validate
If token is valid, create the cookie as you would for a normal user/pass login
Redirect call to original url, sans the token
server.js
// I abstracted the login functionality into one call since it's the same for us
var handleAuthRequest = function handleAuthRequest(auth_body, req, res, next) {
request ({
method: 'POST',
uri: Constants.API_LOGIN_URL,
body: auth_body,
json: true
}, (error, response, body) => {
if (response.statusCode === 200) {
// this makes a cookie with the response of the body (auth token)
ssoUtils.generateCookies(body, res)
// this redirects to the initial url, with the provided cookie.
// Assuming your router already doesn't allow certain components to be accessed
// without a cookie, it would work the same for the login_token auth too.
res.redirect(req.url)
}
else {
next();
}
})
}
// this needs to come before any other routes
app.use((req, res, next) => {
// check if login_token query string was provided
if (req.query.hasOwnProperty('login_token')) {
var {login_token} = req.query
// API call to server to validate token
var jwtToken = jwt.sign({
sub: login_token
}, Constants.API_JWT_SECRET)
// modify the redirect url to remove the token
let parsed = url.parse(req.url)
delete req.query['login_token']
let newUrl = parsed.pathname + '?' + qs.stringify(req.query)
req.url = newUrl
// call the generic login handler
return handleAuthRequest({link_token: jwtToken}, req, res, next)
}
Assuming your server will return the same response from logging in or a valid link token, this would just redirect the call back to whatever your existing process is so no separate functionality client side is needed. As you can see, we also sign the token in a JWT to ensure it's only accepted by the server if sent from our app.
We use React Router to handle our client side routing. Your onEnter check for the initial route would look like this.
routes.js
// token is passed in from our cookie by both the client and server
module.exports = function (token, userAgent, originalUrl) {
function isToken() {
return token !== undefined && token !== null;
}
function ifNoTokenRedirect(nextState, replaceState) {
// check if token is set from cookie
if (!isToken()) {
replaceState({ nextPathname: nextState.location.pathname}, '/signup? redirect=' + originalUrl.pathname);
}
}
return (
// the actual routes
)
}
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.
I'm building an express app and I'd like to know how fancy I can get with middleware. Roughly, I want to accomplish the following with middleware.
Done:
Add requestId to all routes
Authenticate request
Check whether a user has access to a given resource (apart from
authentication)
Not done:
A) Validate parameters for a given route
B) Organize middleware in a sane way if it differs from route to route,
and 3 middlewares are called routinely per route
I have defined my middleware in a separate file, and import it into app.js like so:
var middleware = require('./middleware');
var requestId = middleware.requestId;
var authenticate = middleware.authenticate;
To apply it to all routes I add it to express config:
var app = express.createServer();
app.configure(function () {
app.use(express.logger());
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(requestId); // add requestId to all incoming requests
});
And for route specifics, I add it as an app.get argument:
var routes = require('./v1/routes');
app.get("/v1/foo", routes.foo);
app.get("/v1/bar", authenticate, routes.bar);
Problem A
I'd love to have middleware that I could use to check parameters
validate('x','y','z')
And use it like so for a given route:
app.get("/v1/bar", authenticate, validate('x','y','z'), routes.bar);
Is there a good way to do this? Or should I just be validating on per route basis inside the route definition files?
Problem B
Is there a better way to organize and use my middleware that I should consider?
Update
I'm looking for a way to validate parameters that change a lot between routes. The below obviously don't work- I cannot pass params into the middleware- but is there way where I can define middleware that does this and call it as I've said above?
var validateParams = function (req, res, params, callback) {
// Make sure the required parameters are in the request
console.log('checking for params '+params);
for (var i = 0; i < params.length; i++) {
var param = params[i];
if(!(param in req.query)){
logger.info('cannot find param ['+param+'] in req: '+JSON.stringify(req.query));
res.writeHead(400, {
"Content-Type": "application/json"
});
var out = {
"err": "request missing required parameters"
};
res.end(JSON.stringify(out));
return;
}
}
callback();
}
Problem A
app.get("/v1/bar", authenticate, validate, routes.bar);
function validate(req,res,next){
//Get all parameters here by req.params and req.body.parameter
//validate them and return.
if(validation_true)
next()
}
Problem B
You can use middleware in a way that you don't always need to call authenticate and validate they are called automatically. But that can lead to a mess, for ex. Your middleware then would run on every call, so for SIGNUP/REGISTER there is no point running authenticate.
With validate, sometimes you would need to validate email, sometimes phone no. so both cannot go along.
So using them separate on every call seems the BEST way to me.
You can use express-validation to validate body, query, params, headers and cookies of a request. It responds with errors, if any of the configured validation rules fail.
var validate = require('express-validation'),
Joi = require('joi');
app.post('/login', validate({
body: {
email: Joi.string().email().required(),
password: Joi.string().regex(/[a-zA-Z0-9]{3,30}/).required()
}
}), function(req, res){
res.json(200);
});
This will check if the email and password body params matches the validation rules.
If validation fails it will respond with the following error.
{
"status": 400,
"statusText": "Bad Request",
"errors": [
{
"field": "password",
"location": "body",
"messages": [
"the value of password is not allowed to be empty",
"the value of password must match the regular expression /[a-zA-Z0-9]{3,30}/"
],
"types": [ "any.empty", "string.regex.base" ]
}
]
}
You can also check my repo express-mongoose-es6-rest-api for complete integration.
You could also use a higher-order function (function that returns a function). Thereby passing an array of endpoint specific params to check.
module.export = Class RequestValidator {
static validate(params) {
return function(req, res, next){
for(const param of params) {
validateYourParams here...
if (validation fails) {
return next(new Error());
}
}
next();
}
}
}
And within your routeDefinition you can now call the validation middleware and pass route specific arguments to it.
const RequestValidator = require('your-validation-middleware');
const controller = require('your-controller');
app.post('/path')
.RequestValidator.validate(
[{
name: 'paramName',
type: 'boolean'
},
{
name: 'paramName2',
type: 'string'
}
])
.Controller.handleRequest;