Securely access route with JWT and localstorage - node.js

I'm building a small application where a user logs in and gets redirected to /profile. Right now, I fetch the JWT from localstorage and check it via the server. The server then sends it back to the client to tell me if it's a valid session or not.
jQuery/Client:
UserController.initPanel = () => {
if (session === null) {
window.location = "/";
} else {
UserController.requestAuth(session);
}
};
UserController.requestAuth = (sessionToken) => {
var settings = {
"url": "/api/auth",
"method": "POST",
"headers": {
"Content-Type": "application/json",
"Authorization": `Bearer ${sessionToken}`,
},
"data": ""
}
$.ajax(settings).done(function (response) {
console.log(response);
});
};
Node.js/auth.js route:
router.post("/", (req, res) => {
const authHeader = req.headers.authorization;
if (typeof authHeader !== 'undefined') {
const bearerToken = authHeader.split(' ')[1];
verifyToken(bearerToken, (authData) => {
tokenRequest(authData, (authResponse) => {
handleAuthResponse(req, res, authResponse);
})
});
}
});
const handleAuthResponse = (req, res, authResponse) => {
console.log(authResponse);
return res.status(200).json(authResponse);
}
const verifyToken = (token, cb) => {
jwt.verify(token, 'mysecret', (err, authData) => {
if (err) {
res.sendStatus(403)
} else {
cb(authData);
}
});
}
const tokenRequest = (authHeader, cb) => {
//console.log(authHeader);
var config = {
headers: {'Authorization': `bearer ${authHeader.token}`}
};
axios.get('https://myapi.dev/api/session/me', config)
.then((res) => {
if (res.data.error) {
return response.data
} else {
cb(res.data);
}
})
.catch((error) => {
console.log('error', error);
});
}
I feel like this isn't the correct way to do it. I'm rendering templates with ejs:
router.get("/profile", (req, res) => {
const settings = {
title: "Profile",
revslider: false
};
res.render("profile/profile", { settings: settings } );
});
And if for some reason, JS is disabled, /profile is still accessible. Which isn't that big of a problem, it just feels wrong.
So, is it possible to access /profile route, securely checking for authorization server-side first, before rendering?
Also, auth.js returns some user data I could use in the .ejs template. So that's another reason I'd like to try check auth before rendering as well.
EDIT:
Auth middleware, which I didn't use because I wasn't sure how to pass in the token?
module.exports = (req, res, next) => {
try {
const decoded = jwt.verify(req.body.token, 'mysecret');
req.token = decoded;
} catch (error) {
console.log(error);
return res.status(401).json({
message: 'Auth Failed'
});
}
next();
}

Very basic middleware implementation below which leverages express and express-session.
We basically create a simple function to check req.session exists, within that object, you could have something that identifies whether the user has actually authenticated. I'd recommend you add your own logic here to further check the user status.
const authCheckMiddleware = (req, res, next) => {
// Perform auth checking logic here, which you can attach
// to any route.
if(!req.session) {
return res.redirect('/');
}
next();
};
The authCheckMiddleware can be attached to any route, with app.use or router.use. The req object is passed to all middleware.
// Use the authCheckMiddleware function
router.use('/profile', authCheckMiddleware);
Your router.get('/profile') call is now protected by the above middleware.
// Route protected by above auth check middleware
router.get("/profile", (req, res) => {
const settings = {
title: "Profile",
revslider: false
};
res.render("profile/profile", { settings: settings } );
});

Related

How to get the return value from the hook of fastify?

How to pass parameters from hooks to request handler in Fastify?
For example, I want to get the username in the BasicAuth hook from the request.
const fastify = require('fastify')()
const authenticate = {realm: 'Westeros'}
fastify.register(require('#fastify/basic-auth'), { validate, authenticate })
function validate (username, password, req, reply, done) {
if (username === 'Tyrion' && password === 'wine') {
// How to return username from here?
done()
} else {
done(new Error('Winter is coming'))
}
}
fastify.after(() => {
fastify.addHook('onRequest', fastify.basicAuth)
fastify.get('/', (req, reply) => {
// How to get the username here?
})
})
You need to attach it to the request object:
const fastify = require('fastify')()
const authenticate = { realm: 'Westeros' }
fastify.register(require('#fastify/basic-auth'), { validate, authenticate })
// this decoration will help the V8 engine to improve the memory allocation for the request object
fastify.decorateRequest('user', null)
function validate(username, password, req, reply, done) {
if (username === 'Tyrion' && password === 'wine') {
// add it to the
req.user = { username }
done()
} else {
done(new Error('Winter is coming'))
}
}
fastify.after(() => {
fastify.addHook('onRequest', fastify.basicAuth)
fastify.get('/', async (req, reply) => {
return { hello: req.user.username }
})
})
fastify.inject({
method: 'GET',
url: '/',
headers: {
authorization: 'Basic ' + Buffer.from('Tyrion:wine').toString('base64')
}
}, (err, res) => {
console.log(res.payload)
})

Jwt authorization always giving 403 Forbidden

I am a beginner with node js. I want to make an authentication server using jwt (jsonwebtoken).
The problem is when I test my end point "/api/posts?authorisation=Bearer token..." in postman with method POST with the right token, it gives me forbidden.
Here is my code:
const express = require('express')
const jwt = require('jsonwebtoken')
const app = express()
app.get("/api", (req, res) => {
res.json({
message: "Hey there!!!"
})
})
app.post('/api/posts', verifyToken, (req, res) => {
jwt.verify(req.token, "secretkey", (err, authData) => {
if (err) {
res.sendStatus(403) //forbidden
res.send(`<h2>${err}</h2>`)
} else {
res.json({
message: "Post Created...",
authData
})
}
})
})
app.post('/api/login', (req, res) => {
const user = {
id: 1,
username: "John",
email: "john#gmail.com"
}
jwt.sign({ user: user }, "secretkey", (err, token) => {
res.json({
token
})
})
})
function verifyToken(req, res, next) {
const bearerHeader = req.headers["authorization"]
if (typeof bearerHeader !== "undefined") {
const bearerToken = bearerHeader.split(" ")[1]
req.token = bearerToken
next()
} else {
res.sendStatus(403) //forbidden
}
}
app.listen(5000, () => {
console.log("Server is running :)")
})
I expected it to work because I brought it from a tutorial.
Your code works
The problem is in your request invocation:
According to the oauth2 spec, the Authorization token should be a header and your code expect that
So the token should be sent as http header, not as a query param like foo/bar?authorization=Bearer token...".
Here some samples
Postman
Axios (javascript)
let webApiUrl = 'example.com/getStuff';
let tokenStr = 'xxyyzz';
axios.get(webApiUrl,
{ headers: { "Authorization": `Bearer ${tokenStr}` } });
Advice
Read about oauth2 and jwt
Perform the token validation in the middleware to avoid the validation on each route

How to send Refresh Token for new Access Token to Microsoft Graph (Passport-Azure-AD OIDCStrategy)

I'm having trouble understanding how to send back a refresh token to get a new access token.
I've looked at this documentation: https://developer.microsoft.com/en-us/graph/docs/concepts/nodejs and I basically need help with Authenticate User- step4 but they don't seem to go into more details.
I tried using passport-oauth2-refresh but I think because I'm using Azure AD, I kept getting Error: Cannot register: not an OAuth2 strategy. So I've decided to try to manually check the expiry instead.
I am able to retrieve a refresh token (POST /token with req.user.refreshToken) along with my access token and I store it in json but I don't know how to send it back.
Here is my index.js:
const express = require('express');
const router = express.Router();
const graphHelper = require('../utils/graphHelper.js');
const passport = require('passport');
const request = require('request');
const SERVER = process.env.SERVER;
let user_id = null;
router.get('/', (req, res) => {
if (!req.isAuthenticated()) {
res.render('login');
} else {
renderBotPage(req, res);
}
});
router.get('/login',
passport.authenticate('azuread-openidconnect', {failureRedirect: '/'}),
(req, res) => {
res.redirect('/');
});
router.get('/token',
function (req, res, next) {
passport.authenticate('azuread-openidconnect',
{
response: res, // required
failureRedirect: '/'
}
)
},
function (req, res) {
const options = {
headers: {
'content-type' : 'application/json'
},
method: 'POST',
url: SERVER,
json: {
'accessToken': req.user.accessToken,
'refreshToken': req.user.refreshToken
}
};
request(options,
function (error, response, body) {
if (!error && response.statusCode === 200) {
console.log(body)
}
}
);
console.log('We received a return from AzureAD get token.');
res.redirect('/');
});
router.post('/token',
function (req, res, next) {
passport.authenticate('azuread-openidconnect',
{
response: res, // required
failureRedirect: '/'
}
)(req, res, next);
},
function (req, res) {
console.log('res after first token function', res);
const options = {
headers: {
'content-type' : 'application/json'
},
method: 'POST',
url: SERVER,
json: {
'accessToken': req.user.accessToken,
'refreshToken': req.user.refreshToken
}
};
request(options,
function (error, response, body) {
if (!error && response.statusCode === 200) {
console.log(body)
}
}
);
res.redirect('/');
});
function renderBotPage(req, res) {
graphHelper.getUserData(req.user.accessToken, (err, user) => {
if (!err) {
res.render('chatbotOn', {
display_name: user.body.displayName,
user_id:user.body.id
});
} else {
// Catch of Expired token error
if (hasAccessTokenExpired(err)) {
req.session.destroy(() => {
req.logOut();
res.clearCookie('graphNodeCookie');
res.status(200);
res.redirect('/');
});
}
renderError(err, res);
res.render('chatbotOn', {
display_name: "Random User"
});
}
});
}
router.get('/disconnect', (req, res) => {
req.session.destroy(() => {
req.logOut();
res.clearCookie('graphNodeCookie');
res.status(200);
res.redirect('/');
});
});
function hasAccessTokenExpired(e) {
let expired;
if (!e.innerError) {
expired = false;
} else {
expired = e.forbidden &&
e.message === 'InvalidAuthenticationToken' &&
e.response.error.message === "Le token d'accès a expiré.";
}
return expired;
}
function renderError(e, res) {
e.innerError = (e.response) ? e.response.text : '';
res.render('error', {
error: e
});
}
module.exports = router;
My app.js
const callback = (iss, sub, profile, accessToken, refreshToken, done) => {
done(null, {
profile,
accessToken,
refreshToken
});
};
passport.use(new OIDCStrategy(config.creds, callback));
And here is my graphHelper.js:
const request = require('superagent');
function getUserData(accessToken, callback) {
request
.get('https://graph.microsoft.com/beta/me')
.set('Authorization', 'Bearer ' + accessToken)
.end((err, res) => {
callback(err, res);
});
}
exports.getUserData = getUserData;
here is my config.js:
module.exports = {
creds: {
redirectUrl: 'http://localhost:3000/token',
clientID: 'xxxxx', // regular
clientSecret: 'xxxxx', // regular
identityMetadata:
'xxxxxx',
allowHttpForRedirectUrl: true, // For development only
responseType: 'code id_token',
validateIssuer: false, // For development only
responseMode: 'form_post',
scope: ['openid', 'offline_access', 'Contacts.Read',
'Calendars.ReadWrite'
]
},
};

authentication and tokens node_js

i've got a problem
I'm trying to make a simple login page,
but i've problem with passing the token through http header
app.post('/login',(req,res) => {
var body = req.body.user;
User.findByCredentials(body.email,body.password).then((user) => {
return user.generateAuthToken().then((token) => {
res.header('x-auth', token).send(user);
});
}).catch((e) => {
res.status(400).send();
});
});
here is the route for login page, I saved the token in 'x-auth' in header, and it's work
but...
var authenticate = (req, res, next) => {
var token = req.header('x-auth');
User.findByToken(token).then((user) => {
if (!user) {
return Promise.reject();
}
req.user = user;
req.token = token;
next();
}).catch((e) => {
res.status(401).send();
});
};
module.exports = {authenticate};
this function is middle-ware for privet routes, when I asking for 'x-auth' i've got 'undifined'
here is the piece that connect between both codes
app.get('/',authenticate,(req,res) => {
res.sendFile(publicPath + '/index.html');
});
someone can help me with that?

How to set headers in api call om nodeJs?

I am working on authentication in nodeJs. I have created successfully login API and it works well on the postman. I'm stuck on client side. It does not set token on headers. I am using the passport, jwt for authentication.
My code is:
app.post('/login', (req, res, next) => {
var name = {
name: req.body.name,
password: req.body.password
}
// let m = '';
// console.log(name)
request({
url: "http://localhost:3000/api/login",
method: "POST",
json: true, // <--Very important!!!
body: name
}, function (error, response) {
if (response.body.error == true) {
req.flash('errorMsg', response.body.message);
res.redirect('/');
}
else {
// localStorage.setItem('token', response.body.token);
// console.log(localStorage.getItem('token'))
// req.headers['authorization'] = response.body.token;
// res.setHeader('authorization', response.body.token);
// req.session['token'] = response.body.token;
// console.log(req.session['token'])
// res.set({
// 'Content-Type': 'text/plain',
// 'authorization':response.body.token
// });
// res.setHeader('authorization', response.body.token);
// req.headers['authorization'] = response.body.token;
res.redirect('/secret');
next();
}
});
// console.log(m);
});
and my middleware is:
app.use((req, res, next) => {
var token = req.body.token || req.session['token'] || req.query.token || req.headers['x-access-token'] || localStorage.getItem('token');
req.headers['authorization'] = token;
console.log(req.session['token'], token)
console.log(req.headers['authorization'], config.jwtSecret);
if (token) {
jwt.verify(token, config.jwtSecret, (err, decoded) => {
if (err) {
res.json({
'message': 'Failed to authenticate user'
});
} else {
req.decoded = decoded;
next();
}
});
} else {
// logger.warn('Unauthorized');
return res.sendStatus(401);
}
console.log(req.headers['authorization'])
});
I have tried all possible to set the token in headers but it didn't work well. If I get my token on app.use middleware then I can verify token easily but it didn't allow to set my token.
How can I do this??
Best way!
router.get('/your-route', async (req, res) => {
//...
res.setHeader('your-key', 'your-value');
//..
})
You can see output in header tab in browser or postman
your-key: your-value

Resources