how to save JWT token on client, using Hapi js in node.? - node.js

I want to authenticate a user using JWT token. after a successful validation i am creating a jwt token. I have got some where on internet that i have to send this token to client.how can i save this token and how i can send this token to every request or the token will be automatically attached to the request header? if not then how can i attach this token in request header?. I am using Hapi js with node.
below is my code:
var validateUser = function(request, reply) {
var email = request.payload.email;
var userPassword = request.payload.password;
// get user form database
var user = users[email];
// check if exists
if(!user){
var errMsg = 'User not found';
reply.view('login',{errMsg:errMsg});
}
else {
// validate the user
bcrypt.compare(userPassword, user.password, function(err, isValid) {
if(!isValid){
var errMsg = 'Wrong username or password!';
reply.view('login',{errMsg:errMsg});
}
else {
// if user is found and password is right
// create a token
var token = jwt.sign(user.id, process.env.SECRET_KEY);
// I want to send this token to client, and also want to save this token in header of every request.
reply.redirect('/myNotes');
}
});
}
}

1)after the login store token in local storage(client side)
2)add token to request header using http interceptor.
are you using jquery to call the end points then $ajax setUp is the right place.you can get more details from this post https://www.bennadel.com/blog/2131-using-jquery-ajaxsetup-to-accumulate-global-data-parameters-for-ajax-requests.htm.
if you are consuming services directly from Hapi js then use onpreAuth handler.
If you need any help let me know.i am happy to help

Related

Appwrite authentication - "createJWT is not a function" error

I am trying to build a Node.js server-side signup function for user authentication. The data for the user is being sent via "req.body" and the authentication database is provided by Appwrite.
The signup function should:
Create a user with the credentials provided in the request body.
Return the user details, such as the username and email.
Generate and return a token (cookie/JWT)
I am encountering issues with the Appwrite documentation and would appreciate guidance on building this function.
When trying to POST a new user using the Users API, an error of
createJWT is not a function
is produced, and when using the Account API, an error of
User (role: guests) missing scope (account)
is produced.
Here's the code I have:
const sdk = require('node-appwrite')
const client = sdk.Client()
client
.setEndpoint(endpoint)
.setProject(projectId)
.setKey('...')
const users = sdk.Users(client)
async function signup(req, res) {
try {
const { email, username } = req.body
let { password } = req.body
password = await bcrypt.hash(password, SALT_ROUNDS)
const result = await users.createBcryptUser("unique()", email, password, username)
// Create a token
// Combine data
res.send(userWithToken)
} catch (err) {
error('Failed to signup', err)
throw new Error(err)
}
}
The Users API is intended to be used in an admin perspective rather than as a user. You can use the Account API to execute things on behalf of a user, but the JWT token is typically generated client side and passed to the server, where you can call client.setJWT().

Refresh token how to handle POST routing

I am new to JWT and tokens for user verification and login. I used the following extensions for Node JS (NPM)
var jwt = require('jsonwebtoken');
const cookieParser = require('cookie-parser')
require('dotenv').config();
// Express ..
I already have a login that checks in MongoDB (Node JS as server) the user, checks email and password and then sets a cookie with access token and refresh token.
My login code is like
//create the access token with the shorter lifespan
let accessToken = jwt.sign(payload, process.env.ACCESS_TOKEN_SECRET, {
algorithm: "HS256",
expiresIn: process.env.ACCESS_TOKEN_LIFE
})
//create the refresh token with the longer lifespan
let refreshToken = jwt.sign(payload, process.env.REFRESH_TOKEN_SECRET, {
algorithm: "HS256",
expiresIn: process.env.REFRESH_TOKEN_LIFE
})
//send the access token to the client inside a cookie
res.cookie("_login", accessToken, {secure: true, httpOnly: true})
res.send()
and here is the part for refresh token post
exports.refresh = function (req, res, next){
console.log("Test");
let accessToken = req.cookies._login
if (!accessToken){
return res.status(403).send()
}
let payload
try{
payload = jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET)
}
catch(e){
return res.status(401).send()
}
//retrieve the refresh token from the users array
let refreshToken = payload.email.refreshToken
//verify the refresh token
try{
jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET)
}
catch(e){
return res.status(401).send()
}
let newToken = jwt.sign(payload, process.env.ACCESS_TOKEN_SECRET,
{
algorithm: "HS256",
expiresIn: process.env.ACCESS_TOKEN_LIFE
})
res.cookie("_login", newToken, {secure: true, httpOnly: true})
res.send()
My question now is, since I see in so many tutorial and guides that they also work with refresh token via POST, how do I process that with the user?
Would the client send an AJAX or post to middleware for check on access token
-> If Access token is expired
--> code automatically takes refresh token and issues a new access token and gives an OK?
or the client sends to middleware where access token is checked
-> Access token is expired (result to the user)
-> client make now post request to /refresh-token result = new access and refresh token
-> And again post request to original middleware with new post request?
What is the procedure here, I can't find any workaround how this is handled.
Keep in mind that my answer is based on my experience. Feel free for anyone to edit if I happen to make a mistake in my ways.
So, in order to handle refresh token, I use this way:
When a user successfully logs in, the JWT (containing user's auth) and the refresh token (containing the user's refresh token) will be placed in the user's cookies (same like you).
The user will do his/her things inside your web application, until he/she closes the browser without signing out.
Remember that JWT always have expiration date - this expiration date will be kept in mind.
In every request, you're going to send that specific JWT (that contains the user's auth) and the refresh token to a middleware where you are going to make a POST request. If the JWT has expired, pick the refresh token and call your /refresh-token-result to get a new token. Else, just don't do anything with the refresh token and proceed with your request.
Ensure that your /refresh-token-result accepts a request token. The endpoint will check for its validity and will return a new access token.
If the refresh token had expired, log out the user. This is for security reasons and this is important!
Oh, and when a user logs out, ensure that both of your user's token and your user's refresh token is revoked properly, usually by changing the cookie value and the expiresIn attribute. For me, I usually change both cookie values to loggedOut and I'll set the expiresIn to five seconds.
Alternatively, if you're using React (an additional answer), you can do it like this:
If a user accesses your website, and the JWT expiration date is close to expiry, you can simply use a useEffect() hook in order to renew your access token.
TL;DR: Your second way is already good.
EDIT: Sample pseudocode to help you. Don't copy paste this right away, it most likely wouldn't work, but it should give you the general idea of how things work.
// middleware.js
const { token, refreshToken } = req.cookies;
// 1. If the token has not expired, call 'next()'
// assume 'isExpired' returns boolean: true or false depending on the state of your token.
if (!token.isExpired()) {
return next();
}
// 2. If the token has expired AND the refreshToken has not expired, issue a new token, THEN call 'next()'
if (token.isExpired() && !refreshToken.isExpired()) {
await issueToken();
return next();
}
// 3. Else, logout the user. I'll keep this one short.
await logoutUser();
res.status(401).json({
status: 'fail',
message: 'Your access has expired! Please log in again!',
});
And this is your controller.
// controller.js
const getAllComments = async (req, res, next) => {
const comments = await Comment.find();
res.status(200).json({
status: 'success',
data: comments,
});
}
And, this is what your route should look like.
// this import might be unresolved - keep in mind!
const middleware = require('./middleware');
const getAllComments = require('./controllers');
router.get('/api/v1/comments/', middleware,
checkToken, getAllComments); // assume checkToken is your function to check for a token's validity.
Keep in mind I did not include error handling to keep this example short.

How to send user data object along with token to the angular front-end using node?

At the time of signup and login I want to send User's data along with JWT so that I can update current users profile data.
router.post('/signup', (req, res) => {
let userData = req.body;
let user = new User(userData);
user.save((err, registeredUser) => {
if(err) {
console.log(err);
}else{
let payload = { subject : registeredUser._id }
let token = jwt.sign(payload, 'abcd');
res.status(200).send({token});
}
});
});
How can I send registeredUser object along with token.
Two ways :
Simple and crude: res.status(200).send({token, user})
Expose custom headers from your backend and send the token as part of the response. Then you can use the response body to simply send the user/resource data. res.status(200).send({user}).
Usually #2 is a better design, since the token is more like a auth/session information and can be intercepted using custom middleware/filters (eg:PassportJS) on the backend and always attached as part of the request headers from the front-end using Angular interceptors.
Express config options: headers.https://expressjs.com/en/resources/middleware/cors.html#configuration-options

Node.js express, request jwt token - how to properly get a new valid token if expired in request

On successful login I store the returned JWT token in Session.
Then in my search route I'm accesing the api with the jwt token from the session and set in the header like this:
router.post('/search', (req, res) => {
var getToken = req.session.APIToken;
var auth = 'Bearer '+getToken;
request.get({
headers: {
"authorization": auth
},
url: "localhost/abc/search?name=peter"
}, (error, response, body) => {
if(error) {
return console.dir(error);
}
var jsonBody = JSON.parse(body);
if(jsonBody.status === 200) {
console.log('Request successful!');
}
if(jsonBody.success === false) {
/// ??? BUT what if the JWT token is expired! How do I properly refresh or get a new valid jwt token here? ///
console.log('Token expired!');
}
});
});
BUT what if the JWT token is expired! How do I properly refresh or get a new valid jwt token here?
I suppose with a callback function using the email and password saved in session? But how do I do this exactly or what is the best way to do this?
Edit: I have two apps ... one frontend app ... and one separated API app. So when logging in ... I login in via the frontend app but get the JWT token from the API app. The login session from the frontend app is 360s ... the JWT token is valid for the 60s .... so in case the JWT token expired when the user does a search request ... I want to automatically generate a new token that finishes the request.
I think you are trying to implement auth0 with JWT token. The best practice is to always return 2 tokens when login:
access_token (Using JWT): The benefit of JWT is that the server doesn't need to query back to database to verify and get information about the user who is calling the API
refresh_token: This is useful for the user to regenerate the token when the access_token is expired.
The the client-side application, you should always save the 2 tokens. Before calling any API, you should check if the JWT is expired. It can be done easily using jwt.decode(). The expired information is usually stored in the exp or iat key. If It's expired, you need to call the API to regenerate the token before calling the actual API.
You can find an example here: https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/
Hope this helps.

Authentication on Server side routes in Meteor

What is the best way (most secure and easiest) to authenticate a user for a server side route?
Software/Versions
I'm using the latest Iron Router 1.* and Meteor 1.* and to begin, I'm just using accounts-password.
Reference code
I have a simple server side route that renders a pdf to the screen:
both/routes.js
Router.route('/pdf-server', function() {
var filePath = process.env.PWD + "/server/.files/users/test.pdf";
console.log(filePath);
var fs = Npm.require('fs');
var data = fs.readFileSync(filePath);
this.response.write(data);
this.response.end();
}, {where: 'server'});
As an example, I'd like to do something close to what this SO answer suggested:
On the server:
var Secrets = new Meteor.Collection("secrets");
Meteor.methods({
getSecretKey: function () {
if (!this.userId)
// check if the user has privileges
throw Meteor.Error(403);
return Secrets.insert({_id: Random.id(), user: this.userId});
},
});
And then in client code:
testController.events({
'click button[name=get-pdf]': function () {
Meteor.call("getSecretKey", function (error, response) {
if (error) throw error;
if (response)
Router.go('/pdf-server');
});
}
});
But even if I somehow got this method working, I'd still be vulnerable to users just putting in a URL like '/pdf-server' unless the route itself somehow checked the Secrets collection right?
In the Route, I could get the request, and somehow get the header information?
Router.route('/pdf-server', function() {
var req = this.request;
var res = this.response;
}, {where: 'server'});
And from the client pass a token over the HTTP header, and then in the route check if the token is good from the Collection?
In addition to using url tokens as the other answer you could also use cookies:
Add in some packages that allow you to set cookies and read them server side:
meteor add mrt:cookies thepumpinglemma:cookies
Then you could have something that syncs the cookies up with your login status
Client Side
Tracker.autorun(function() {
//Update the cookie whenever they log in or out
Cookie.set("meteor_user_id", Meteor.userId());
Cookie.set("meteor_token", localStorage.getItem("Meteor.loginToken"));
});
Server Side
On the server side you just need to check this cookie is valid (with iron router)
Router.route('/somepath/:fileid', function() {
//Check the values in the cookies
var cookies = new Cookies( this.request ),
userId = cookies.get("meteor_user_id") || "",
token = cookies.get("meteor_token") || "";
//Check a valid user with this token exists
var user = Meteor.users.findOne({
_id: userId,
'services.resume.loginTokens.hashedToken' : Accounts._hashLoginToken(token)
});
//If they're not logged in tell them
if(!user) return this.response.end("Not allowed");
//Theyre logged in!
this.response.end("You're logged in!");
}, {where:'server'});
I think I have a secure and easy solution for doing this from within IronRouter.route(). The request must be made with a valid user ID and auth token in the header. I call this function from within Router.route(), which then gives me access to this.user, or responds with a 401 if the authentication fails:
// Verify the request is being made by an actively logged in user
// #context: IronRouter.Router.route()
authenticate = ->
// Get the auth info from header
userId = this.request.headers['x-user-id']
loginToken = this.request.headers['x-auth-token']
// Get the user from the database
if userId and loginToken
user = Meteor.users.findOne {'_id': userId, 'services.resume.loginTokens.token': loginToken}
// Return an error if the login token does not match any belonging to the user
if not user
respond.call this, {success: false, message: "You must be logged in to do this."}, 401
// Attach the user to the context so they can be accessed at this.user within route
this.user = user
// Respond to an HTTP request
// #context: IronRouter.Router.route()
respond = (body, statusCode=200, headers) ->
this.response.statusCode statusCode
this.response.setHeader 'Content-Type', 'text/json'
this.response.writeHead statusCode, headers
this.response.write JSON.stringify(body)
this.response.end()
And something like this from the client:
Meteor.startup ->
HTTP.get "http://yoursite.com/pdf-server",
headers:
'X-Auth-Token': Accounts._storedLoginToken()
'X-User-Id': Meteor.userId()
(error, result) -> // This callback triggered once http response received
console.log result
This code was heavily inspired by RestStop and RestStop2. It's part of a meteor package for writing REST APIs in Meteor 0.9.0+ (built on top of Iron Router). You can check out the complete source code here:
https://github.com/krose72205/meteor-restivus
Because server-side routes act as simple REST endpoints, they don't have access to user authentication data (e.g. they can't call Meteor.user()). Therefore you need to devise an alternative authentication scheme. The most straightforward way to accomplish this is with some form of key exchange as discussed here and here.
Example implementation:
server/app.js
// whenever the user logs in, update her apiKey
Accounts.onLogin(function(info) {
// generate a new apiKey
var apiKey = Random.id();
// add the apiKey to the user's document
Meteor.users.update(info.user._id, {$set: {apiKey: apiKey}});
});
// auto-publish the current user's apiKey
Meteor.publish(null, function() {
return Meteor.users.find(this.userId, {fields: {apiKey: 1}});
});
lib/routes.js
// example route using the apiKey
Router.route('/secret/:apiKey', {name: 'secret', where: 'server'})
.get(function() {
// fetch the user with this key
// note you may want to add an index on apiKey so this is fast
var user = Meteor.users.findOne({apiKey: this.params.apiKey});
if (user) {
// we have authenticated the user - do something useful here
this.response.statusCode = 200;
return this.response.end('ok');
} else {
// the key is invalid or not provided so return an error
this.response.statusCode = 403;
return this.response.end('not allowed');
}
});
client/app.html
<template name="myTemplate">
{{#with currentUser}}
secret
{{/with}}
</template>
Notes
Make /secret only accessible via HTTPS.
While it's very likely that the user requesting /secret is currently connected, there is no guarantee that she is. The user could have logged in, copied her key, closed the tab, and initiated the request sometime later.
This is a simple means of user authentication. I would explore more sophisticated mechanisms (see the links above) if the server-route reveals high-value data (SSNs, credit cards, etc.).
See this question for more details on sending static content from the server.
I truly believe using HTTP headers are the best solution to this problem because they're simple and don't require messing about with cookies or developing a new authentication scheme.
I loved #kahmali's answer, so I wrote it to work with WebApp and a simple XMLHttpRequest. This has been tested on Meteor 1.6.
Client
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
// Skipping ahead to the upload logic
const xhr = new XMLHttpRequest();
const form = new FormData();
// Add files
files.forEach((file) => {
form.append(file.name,
// So BusBoy sees as file instead of field, use Blob
new Blob([file.data], { type: 'text/plain' })); // w/e your mime type is
});
// XHR progress, load, error, and readystatechange event listeners here
// Open Connection
xhr.open('POST', '/path/to/upload', true);
// Meteor authentication details (must happen *after* xhr.open)
xhr.setRequestHeader('X-Auth-Token', Accounts._storedLoginToken());
xhr.setRequestHeader('X-User-Id', Meteor.userId());
// Send
xhr.send(form);
Server
import { Meteor } from 'meteor/meteor';
import { WebApp } from 'meteor/webapp';
import { Roles } from 'meteor/alanning:roles'; // optional
const BusBoy = require('connect-busboy');
const crypto = require('crypto'); // built-in Node library
WebApp.connectHandlers
.use(BusBoy())
.use('/path/to/upload', (req, res) => {
const user = req.headers['x-user-id'];
// We have to get a base64 digest of the sha256 hashed login token
// I'm not sure when Meteor changed to hashed tokens, but this is
// one of the major differences from #kahmali's answer
const hash = crypto.createHash('sha256');
hash.update(req.headers['x-auth-token']);
// Authentication (is user logged-in)
if (!Meteor.users.findOne({
_id: user,
'services.resume.loginTokens.hashedToken': hash.digest('base64'),
})) {
// User not logged in; 401 Unauthorized
res.writeHead(401);
res.end();
return;
}
// Authorization
if (!Roles.userIsInRole(user, 'whatever')) {
// User is not authorized; 403 Forbidden
res.writeHead(403);
res.end();
return;
}
if (req.busboy) {
// Handle file upload
res.writeHead(201); // eventually
res.end();
} else {
// Something went wrong
res.writeHead(500); // server error
res.end();
}
});
I hope this helps someone!
Since Meteor doesn't use session cookies, client must explicitly include some sort of user identification when making a HTTP request to a server route.
The easiest way to do it is to pass userId in the query string of the URL. Obviously, you also need to add a security token that will prove that the user is really who the claim they are. Obtaining this token can be done via a Meteor method.
Meteor by itself doesn't provide such mechanism, so you need some custom implementation. I wrote a Meteor package called mhagmajer:server-route which was thoroughly tested. You can learn more about it here: https://blog.hagmajer.com/server-side-routing-with-authentication-in-meteor-6625ed832a94

Resources