I am using the Postman to test my NodeJS application. I have written an authentication code that gets stuck at the middle of the code with just showing sending request.
How can I find the problem and resolve it?
This is my auth code:
require('dotenv').config();
const passport = require('passport');
const passportLocal = require('passport-local');
const LocalStrategy = passportLocal.Strategy;
const passportJWT = require('passport-jwt');
const JWTStrategy = passportJWT.Strategy;
const ExtractJWT = passportJWT.ExtractJwt;
var FacebookTokenStrategy = require('passport-facebook-token');
constJWT = require('jsonwebtoken');
const User = require('../model/User');
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
var accessToken = null;
var refreshToken = null;
const handleLogin = async (req, res) => {
const cookies = req.cookies; //['jwt'];
console.log(`cookie available at login: ${JSON.stringify(cookies)}`);
const { username, password } = req.body;
if (!username || !password) return res.status(400).json({ 'message': 'Username and password are required.' });
const foundUser = await User.findOne({ username: username }).exec();
if (!foundUser) { console.log("a problem here");
return res.sendStatus(401);} //Unauthorized
// evaluate password
// const match = await bcrypt.compare(password, foundUser.password);
if (password) {
foundUser.authenticate(password, async function (err, model, passwordError) {
if (passwordError) {
console.log(err)
res.send('The given password is incorrect!!');
} else if (model) {
console.log(`correct password ${model}`)
// res.send('You are authenticated');
const roles = Object.values(foundUser.roles).filter(Boolean);
// createJWTs
var accessToken = JWT.sign(
{
"UserInfo": {
"username": foundUser.username,
"roles": roles
}
},
process.env.ACCESS_TOKEN_SECRET,
{ expiresIn: '10m' }
);
newRefreshToken = JWT.sign(
{ "username": foundUser.username },
process.env.REFRESH_TOKEN_SECRET,
{ expiresIn: '90d' }
);
// Changed to let keyword
let newRefreshTokenArray =
!cookies?.jwt
? foundUser.refreshToken
: foundUser.refreshToken.filter(rt => rt !== cookies.jwt);
console.log("These are tokens" , accessToken , refreshToken, newRefreshTokenArray);
if (cookies?.jwt) {
/*
Scenario added here:
1) User logs in but never uses RT and does not logout
2) RT is stolen
3) If 1 & 2, reuse detection is needed to clear all RTs when user logs in
*/
refreshToken = cookies.jwt;
const foundToken = await User.findOne({ refreshToken }).exec();
// Detected refresh token reuse!
if (!foundToken) {
console.log('attempted refresh token reuse at login!')
// clear out ALL previous refresh tokens
newRefreshTokenArray = [];
}
res.clearCookie('jwt', { httpOnly: true, sameSite: 'None', secure: true });
}
// Saving refreshToken with current user
foundUser.refreshToken = [...newRefreshTokenArray, newRefreshToken];
const result = await foundUser.save();
console.log(result);
console.log(roles);
// Creates Secure Cookie with refresh token
res.cookie('jwt', newRefreshToken, { httpOnly: true, secure: true, sameSite: 'None', maxAge: 24 * 60 * 60 * 1000 });
// Send authorization roles and access token to user
res.json({ roles, accessToken });
}
});
} else {
res.send('Please input your account password!!');
}
}
And I can also see this result on the VSCOde console:
Connected to MongoDB
Server running on port 3000
POST /auth
cookie available at login: {}
correct password {
roles: { User: 2001 },
_id: new ObjectId("629e44ba19e7596e27156cbf"),
email: 'q#q.com',
username: 'q#q.com',
password: '111111',
refreshToken: [],
salt: '1add546c232166135e1b6b16187bf3e6e321a828ad3d869c6c77003e6b279acd',
hash: 'c27155c0aaf5f8ed241c07f9d16100b261fdc53db3641b7941ec45606b4d816880f03f4d0b35871a48277c2e9a768e2683e9a506d3ffc180d9a9f326d7ec984075662b1668b2720cd0dda4cf88f4442dee6f5c06a5b94c52638bb80b81117992d297e50d7709ccfcc4cded1cf3d8f6265e27ac09ad91b2bcbb562c6772ff99792104a647dfd1ffcbbb8fac6263d607a4116ac5271573874100fba4c827f78679758d6a62ee19b08a03026c0efde1c99c57aa1cc980eb53d1062d0d87c137a73673a1a8bfd0615d790935c3c82c080c875b118f9e20110e63d7fef7701af894aad34bccf366d46ac5ad4dd0214d19727fdb01a980912fe80462131a5da208832bfc458443ba34cfbdf7486bb9b3b9cda24c7126461c7ce1064d394b0246f9f4b8bb3882b938334f1c7838f299b8386da416ed36d9339b81dbcdb2c44e3d85860c09a0a86d14b08976ef927c6b3284f78c12d12ccbf1cf5aa4c74cdc5ff47dec8162946a681d5bf4ffc547d512fcbcedc5cd6b30148e0c77fbacf002b1cf380f4b27f250698cdc51a467704088339ae0911583a6c6d38793ae1a64f8f1355b51480a657f303970c2523df377b11730932e2d4579323acb96b1ae395adc95a32e2d813317d0fd57f0489f02d0e7fb48da64119a548c8cb1705f4a996430b14e087d58cc8549a8cf6f0d698ad0a7e8db17b46bb146f897ba8557a1ca75921d27a4c6',
__v: 0
}
Related
I am new to refreshToken and also new to cookies.
I am using graphql and I am trying to store refreshToken on cookies when user is logging in.
The problem is nothing happned in my cookies when I am loggin in :)
My server -
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
context: ({ req, res }) => ({ req, res }),
});
app.use(cookieParser());
Login-
login: async (_, { username, password }, { res }) => {
const user = await User.findOne({ username }).select('+password');
const refreshToken = jwt.sign({ id: user._id }, 'nollieHeelFlip', {
expiresIn: '7d',
});
res.cookie('refresh-token', refreshToken, { expire: 60 * 60 * 24 * 7 });
return {
...user._doc,
id: user._id,
token: user.createJWT(),
};
},
Solved this by using cors and crednetials: inclue.
The problem now is I can't access cookies from my auth util.
export const auth = async (context) => {
const authHeader = context.req.headers.authorization;
const refreshToken = context.req.cookies['refresh-token'];
console.log(refreshToken);
I can see the refresh-token in my cookies but I get undefined.
I am trying to do an authentication system using JWT token in Express, using passport-jwt and jsonwebtoken. Everything works well now, but even if I set an expire date the token remains valid even after that date, if I try to use it with Postman, I can't figure out what am I missing.
From my understanding, and from what I saw in many tutorials, expiration date should be handled by passsport-jwt itself, but if I try to use a token generated some days ago it still works, even if I did set the duration to one day. Here's my code:
//passport.jwt.ts
const publicKey: string = process.env.PUBLIC_KEY.replace(/\\n/g, "\n");
const options = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: publicKey,
algorithms: ["RS256"],
};
const strategy = new JWTStrategy(options, async (payload, done) => {
const repo: UserRepository = RepoManager.getUserRepo();
try {
const user: UserModel = await repo.getUserById(payload.sub);
if (user) {
return done(null, user);
} else {
return done(null, false);
}
} catch (error) {
done(error);
}
});
export function JWTPassport(passport) {
passport.use(strategy);
}
//register user middleware for the /register route
export async function registerUser(req, res) {
try {
const { salt, hash } = hashPassword(req.body.password);
const newUser = {
username: req.body.username,
password: hash,
salt: salt,
email: req.body.email,
};
let model: UserModel = Deserialize(newUser);
const repo: UserRepository = RepoManager.getUserRepo();
model = await repo.createUser(model);
const jwt = issueJWT(model);
res.status(200).json({ user: model, jwt: jwt });
} catch (e) {
res
.status(400)
.json({ error: "could not register user" });
}
}
//function generating the JWT token
export function issueJWT(user: UserModel) {
const id = user.id;
const expiresIn = "1d";
const payload = {
sub: id,
iat: Date.now(),
};
const signedToken = sign(payload, privateKey, {
expiresIn: expiresIn,
algorithm: "RS256",
});
return {
token: "Bearer " + signedToken,
expires: expiresIn,
};
}
EDIT: I figured out part of the problem, the timestamps were being generated in milliseconds instead of seconds. Now I manually calculate the timestamps with 60 * 60 * 24, but I'd like to understand why the syntax "1d" since it is recommended in the docs
The same problem. The problem in iat date. Try to set iat like this:
const expiresIn = "20s";
const payload = {
sub: id,
iat: Math.floor(Date.now() / 1000),
};
const signedToken = jsonwebtoken.sign(payload, ACCESS_TOKEN_PRIV_KEY, {
expiresIn: expiresIn,
algorithm: "RS256",
});
Try this in issue Jwt
const signedToken = jwt.sign(payload, privateKey, {
expiresIn: "1h",
algorithm: "RS256",
});
I have a simple program with a Auth and Todo-App like "Microservice".
I've implemented a basic auth flow:
User logs in with credentials
Gets a token back which expires in 15 minutes
A httpOnly cookie is set with the refresh token
User can now call /todos route passing the token as a req body
Client App (WebBrowser) automatically calls /refresh_token, to renew the token and refresh_token
Do I also need to store refresh tokens in a Users db and validate them on each /refresh_token request? Would there be any more good security practices or improvements to the way I have implemented the auth flow?
AuthService
const express = require('express');
const jwt = require('jsonwebtoken');
const cookieParser = require('cookie-parser');
const PORT = 3001;
const app = express();
app.use(cookieParser('secret3'));
const user = {
id: 1,
username: 'sam',
password: '123',
ref_token: '' //
};
//Generate token and refresh token
app.post('/login', (req, res) => {
const payload = {
id: user.id
};
const token = jwt.sign(payload, 'secret', { expiresIn: '15m' });
const refreshToken = jwt.sign(payload, 'secret2', { expiresIn: '60d' });
//Save refresh token to users db row
//Set refresh token in httpOnly cookie
let options = {
maxAge: 1000 * 60 * 60 * 24 * 30, // would expire after 1month
httpOnly: true,
signed: true
};
res.cookie('rt', refreshToken, options);
res.json({
token: token,
message: 'Login successful'
});
});
//Generates a new jwt and a refresh token from prev refresh token
app.get('/refresh_token', (req, res) => {
//Get the refresh token from cookie
const { rt } = req.signedCookies;
if (rt == null) {
return res.json({
message: 'Missing rt cookie'
});
}
//Verify refresh token against users db here...
//New authtoken and refreshtoken
const payload = {
id: user.id
};
const token = jwt.sign(payload, 'secret', { expiresIn: '15m' });
const refreshToken = jwt.sign(payload, 'secret2', { expiresIn: '60d' });
//Update new refreshToken in DB
//Set refresh token in httpOnly cookie
let options = {
maxAge: 1000 * 60 * 60 * 24 * 30, // would expire after 1month
httpOnly: true,
signed: true
};
res.cookie('rt', refreshToken, options);
res.json({
token: token,
message: 'New tokens generated'
});
});
app.listen(PORT, () => {
console.log(`Auth microservice running on ${PORT}`)
});
TodoService
const express = require('express');
const verifyToken = require('./verifyjwt.js');
const bodyParser = require('body-parser');
const PORT = 3002;
const app = express();
app.use(bodyParser());
const todos = [
{
id: 1,
belongsTo: 1,
content: 'Buy milk',
isDone: true
},
{
id: 346457,
belongsTo: 5436,
content: 'Clean your desktop',
isDone: false
}
];
//Return user todos (protected route)
app.get('/todos', verifyToken,(req, res) => {
//Find where req.decoded.id matches todos.belongsTo...
res.send(todos[0]);
});
app.listen(PORT, () => {
console.log(`Todo microservice running on port ${PORT}`);
});
verifyjwt.js
const jwt = require('jsonwebtoken');
module.exports = (req,res,next) => {
const token = req.body.token;
//Decode token
if (token) {
//Verify secret and exp
jwt.verify(token, 'secret', function(err, decoded) {
if (err) {
return res.status(401).json({"message": 'Unauthorized access'});
}
req.decoded = decoded;
next();
});
} else {
return res.status(403).send({
"message": 'No token provided'
});
}
};
What you've done mostly looks good. As you're using JWTs there is no need to look the user up in the database as only you know the JWT secret, so by verifying the JWT you can then use the decoded user id and know that they are the correct user.
The only thing you might consider using a database for is to invalidate unexpired tokens, e.g. if the user logs out which you could probably do simply by storing the last logout time in the database, then before issuing a new refresh token, check the last logout time agains the issue time of the previous token.
Only thing you need to change, which I'm sure is deliberate for debugging, is to use the req.decoded.id value for the user id and not the hardcoded one.
One other thing I noticed in your code is in your jwt.verify function you return your unauthorised message inside a callback, which I think will just get swallowed, causing the request to hang if the token is invalid.
const JwtStrategy = require('passport-jwt').Strategy,
ExtractJwt = require('passport-jwt').ExtractJwt;
const User = require('../models/User');
const key = require('./keys').secret;
const mongoose = require('mongoose');
var opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = key;
module.exports = (passport) => {
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
console.log(jwt_payload);
User.findById(jwt_payload.id, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
return done(null, user);
} else {
return done(null, false);
}
});
}))
}
I am using passport-jwt stratergy to validate with token but this is file is not running properly i even tried to console the payload here but this is not even logging it
passport.use(new JwtStrategy(opts, function(jwt_payload, done)
console.log(jwt_payload);
Token Stratergy
const payload = {
id: user.id,
username: user.username,
name: user.name
}
const token = jwt.sign(payload, config.secret, { expiresIn: 36000 });
return res.json({
success: true,
token: 'JWT ' + token,
user: {
id: user.id,
name: user.name,
username: user.username
}
})
And when i am using this on the protected routes it is not authorizing the protected routes
Result = Unauthorized
router.get('/profile', passport.authenticate('jwt', { session: false }), (req, res) => {
res.send("Profile");
});
The reason your authentication is not working is that you've used the legacy token prefix "JWT" and a modern JWT (fromAuthHeaderAsBearerToken) extractor as an option to your strategy.
You can do one of the steps to fix the problem:
change the token prefix to 'Bearer ' + token
change the token extractor:
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme("JWT");
I am using express & jwt-simple to handle login/register & authenticated requests as a middleware api. I'm trying to create a .well-known endpoint so other api's can authenticate request based on token send in.
Here's my strategy:
module.exports = function() {
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeader();
opts.secretOrKey = securityConfig.jwtSecret;
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
// User.where('id', jwt_payload.id).fetch({withRelated: 'roles'})
console.log('jwt_payload', jwt_payload)
User.where('id', jwt_payload.id).fetch()
.then(user => user ? done(null, user) : done(null, false))
.catch(err => done(err, false));
}));
};
Here's my login route:
router.post('/login', function(req, res) {
const {username, password} = req.body;
Promise.coroutine(function* () {
const user = yield User.where('username', username).fetch();
if(user) {
const isValidPassword = yield user.validPassword(password);
if (isValidPassword) {
let expires = (Date.now() / 1000) + 60 * 30
let nbf = Date.now() / 1000
const validatedUser = user.omit('password');
// TODO: Verify that the encoding is legit..
// const token = jwt.encode(user.omit('password'), securityConfig.jwtSecret);
const token = jwt.encode({ nbf: nbf, exp: expires, id: validatedUser.id, orgId: validatedUser.orgId }, securityConfig.jwtSecret)
res.json({success: true, token: `JWT ${token}`, expires_in: expires});
} else {
res.status(401);
res.json({success: false, msg: 'Authentication failed'});
}
} else {
res.status(401);
res.json({success: false, msg: 'Authentication failed'});
}
})().catch(err => console.log(err));
});
Here's my .well-known route:
router.get('/.well-known', jwtAuth, function(req, res) {
// TODO: look over res.req.user. Don't seem to be the way to get those parameters.
// We dont take those parameters from the decrypted JWT, we seem to grab it from the user in DB.
const { id, orgId } = res.req.user.attributes;
console.log("DEBUG: userId", id)
console.log("DEBUG: USER", res.req.user)
res.json({
success: true,
userId: id,
orgId
});
});
here's my jwtAuth() function:
const passport = require('passport');
module.exports = passport.authenticate('jwt', { session: false });
How would I actually get the token in the route function & decrypt it? All this does right now which works is that it authenticates if true however I need to be able to decrypt the token to send back the stored values. I'm not sure what res.req.user.attributes comes from, is this the token?
Take a look at passport-jwt and in your passport-config (or wherever you initialize passport) setup JWT Strategy:
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const jwtAuth = (payload, done) => {
const user = //....find User in DB, fetch roles, additional data or whatever
// do whatever with decoded payload and call done
// if everything is OK, call
done(null, user);
//whatever you pass back as "user" object will be available in route handler as req.user
//if your user does not authenticate or anything call
done(null, false);
}
const apiJwtOptions: any = {};
apiJwtOptions.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
apiJwtOptions.algorithms = [your.jwt.alg];
apiJwtOptions.secretOrKey = your.jwt.secret;
//apiJwtOptions.issuer = ???;
//apiJwtOptions.audience = ???;
passport.use('jwt-api', new JwtStrategy(apiJwtOptions, jwtAuth));
If you want just decoded token, call done(null, payload) in jwtAuth.
Then in your route files when you want to protect endpoints and have info about user, use as:
const router = express.Router();
router.use(passport.authenticate('jwt-api', {session: false}));
And in handler you should have req.user available. It is configurable to what property of req you store data from auth, req.user is just default.