refreshToken using cookies in graphql - node.js

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.

Related

Why the Postman shows "sending request` and the application gets stuck there?

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
}

My app is blocked using Google Strategy with PassportJs and MERN

i develop an app using MERN stack and PassportJs for authentication. I used Google strategy to allow user authentication but i'm faced issues.
Here are the errors that appear in the console:
Access to fetch at 'https://tagsite.herokuapp.com/users/google' from origin 'https://tagsite.netlify.app' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
GET https://tagsite.herokuapp.com/users/google net::ERR_FAILED
How can i fix it? Some help please.
I already use cors package in backend, but still have same errors.
Here is my code.
Backend
app.js
const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const passport = require('passport');
const serverless = require('serverless-http');
if (process.env.NODE_END !== 'production') {
// Load environment variables from .env file for non production environment
require('dotenv').config()
}
// Connect database
require('./utils/connectdb')
// Strategies
require('./strategies/GoogleStrategy')
require('./utils/authenticate')
// Routes import
const userRoutes = require('./routes/user')
const app = express();
app.use(bodyParser.json())
app.use(cookieParser(process.env.COOKIE_SECRET))
app.use(cors())
app.use(passport.initialize())
// Use routes
app.use('/users', userRoutes)
app.get('/', (req, res) => {
res.send({ status: 'success' })
})
// Server set up
let port;
if (process.env.PORT) {
port = process.env.PORT
} else {
port = 8000
}
app.listen(port, () => {
console.log('Listen on port: ' + port)
})
module.exports.handler = serverless(app)
GoogleStrategy.js
const passport = require('passport')
const googleStrategy = require('passport-google-oauth20').Strategy
const User = require('../models/user')
const slug = require('slug')
require('dotenv').config()
// serialize & deserialize user
passport.serializeUser((user, done) => {
done(null, user.id)
// done(null, user)
})
passport.deserializeUser((id, done) => {
User.findById(id)
.then((user) => {
done(null, user)
})
// done(null, user)
})
passport.use(
new googleStrategy(
{
clientID: process.env.GOOGLE_APP_ID,
clientSecret: process.env.GOOGLE_APP_SECRET,
callbackURL: process.env.GOOGLE_APP_CALLBACK_URL
},
(accessToken, refreshToken, profile, done) => {
User.findOne({ providerId: profile.id })
.then((user) => {
if (user) {
const userData = {
username: profile.displayName,
provider: profile.provider,
providerId: profile.id,
email: profile.emails[0].value,
avatar: profile._json.picture
&& profile._json.picture,
token: accessToken,
slug: slug(profile.displayName)
}
user.refreshToken.push({ refreshToken: accessToken })
user.save()
done(null, userData)
} else {
new User({
username: profile.displayName,
provider: profile.provider,
providerId: profile.id,
email: profile.emails[0].value,
avatar: profile._json.picture
&& profile._json.picture,
$addToSet: { refreshToken: accessToken },
slug: slug(profile.displayName)
})
.save()
.then((user) => {
const userData = {
username: profile.displayName,
provider: profile.provider,
providerId: profile.id,
email: profile.emails[0].value,
avatar: profile._json.picture
&& profile._json.picture,
token: accessToken,
slug: slug(profile.displayName)
}
user.refreshToken.push({ refreshToken: accessToken })
user.save()
done(null, userData)
})
.catch((error) => {
console.log(`create new user failed: ${error}`)
})
}
})
}
)
)
user.js (controllers)
const passport = require('passport');
module.exports = {
googleLogin: [
passport.authenticate('google', { scope: ['profile', 'email'] })
],
googleLoginCallback : [
passport.authenticate('google', {
failureRedirect: process.env.AUTH_FAILURE_REDIRECT_URL,
session: false
}),
(req, res, next) => {
const token = req.user.token
// res.status(200).json({userData: req.user})
res.cookie('refreshToken', req.user.token, COOKIE_OPTIONS)
res.send({ success: true, token })
}
],
user.js (routes)
const router = require('express').Router()
// Controllers
const {
googleLogin, googleLoginCallback
} = require('../controllers/user')
router.get('/google', googleLogin)
router.get('/google/callback', googleLoginCallback)
module.exports = router
Frontend
google.js (component)
import { Button, Callout } from '#blueprintjs/core'
import React, { useContext, useState } from 'react'
import { UserContext } from '../../utils/context/UserContext'
const GoogleLogin = () => {
const [error, setError] = useState(null)
const [userContext, setUserContext] = useContext(UserContext)
const loginGoogle = () => {
setError(null)
const genericErrorMessage = 'Something error'
fetch(`${process.env.REACT_APP_API_URL}/users/google`, {
method: 'GET',
// credentials: 'include',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
})
.then(async (res) => {
if (!res.ok) {
if (res.status === 400) {
setError('Please try again')
} else if (res.status === 401) {
setError('Unauthorized')
} else {
setError(genericErrorMessage)
}
} else {
const data = await res.json()
setUserContext((oldValues) => {
return { ...oldValues, token: data.token }
})
}
})
.catch((err) => {
setError(genericErrorMessage)
})
}
return (
<>
{error && <Callout intent="danger">{error}</Callout>}
<Button text="login with google" onClick={loginGoogle} />
</>
)
}
export default GoogleLogin
app.use(
cors({
origin: [
"https://tagsite.netlify.app"
],
credentials: true,
methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
})
);
Like your error is saying, CORS doesn't like your Front End Code. It doesnt know it. You have to introduce your Front End to your Backend, and tell your Backend to let certain origins access your server.
I'm not experienced enough on whether you have to include credentials or not, but it works for me!

How do I forward authorization with the user JWT token in the header and compare it to the JWT token in the cookie to Auth user?

I have a problem with the logged in user, when I refresh the page the user gets lost. This is how I assign a JWT token:
const signToken = id => {
return jwt.sign({ id }, 'my-ultra-secure-and-ultra-long-secret', {
expiresIn: '14d',
});
};
This is how I send a token to a cookie with this function as well:
const createSendToken = (user, statusCode, res) => {
const token = signToken(user._id);
const cookieOptions = {
expires: new Date(Date.now() + 14 * 1000 * 60 * 24),
httpOnly: true,
};
res.cookie('jwt', token, cookieOptions);
// Remove password from output
user.password = undefined;
res.status(statusCode).json({
status: 'success',
token,
data: {
user,
},
});
};
This is my login controller:
exports.login = catchAsync(async (req, res, next) => {
const { email, password } = req.body;
// 1) Check if email and password exist
if (!email || !password) {
return next(new AppError('Please provide email and password!', 400));
}
// 2) Check if user exists && password is correct
const user = await User.findOne({ email }).select('+password');
if (user && (await user.correctPassword(password, user.password))) {
createSendToken(user, 200, res);
} else {
return next(new AppError('Incorrect email or password', 401));
}
});
This is my Protect controller (protect middleware):
exports.protect = catchAsync(async (req, res, next) => {
// 1) Getting token and check of it's there
let token;
if (
req.headers.authorization &&
req.headers.authorization.startsWith('Bearer')
) {
token = req.headers.authorization.split(' ')[1];
}
if (!token) {
return next(
new AppError('You are not logged in! Please log in to get access.', 401)
);
}
// 2) Verification token
const decoded = await promisify(jwt.verify)(
token,
'my-ultra-secure-and-ultra-long-secret'
);
// 3) Check if user still exists
const currentUser = await User.findById(decoded.id);
if (!currentUser) {
return next(
new AppError(
'The user belonging to this token does no longer exist.',
401
)
);
}
// 4) Check if user changed password after the token was issued
if (currentUser.changedPasswordAfter(decoded.iat)) {
return next(
new AppError('User recently changed password! Please log in again.', 401)
);
}
// GRANT ACCESS TO PROTECTED ROUTE
req.user = currentUser;
res.locals.user = currentUser;
next();
});
This is my private route with this middleware:
router.route('/:id').get(authController.isLoggedIn, postController.getPost);
The problem is when I log in I get a cookie, but I can't access the protected route (I get an error token is undefind). When I refresh the page the user is lost but the cookie remains in storage. When I try to access the protect route via postman and when I add the Authorization Bearer to the header ..... (token) I can access it.
This is my frontend user reducer:
export const userLoginReducer = (state = {}, action) => {
switch (action.type) {
case USER_LOGIN_REQUEST:
return { loading: true, isAuthenticated: false };
case USER_LOGIN_SUCCESS:
return {
loading: false,
isAuthenticated: true,
user: action.payload,
};
case USER_LOGIN_FAIL:
return { loading: false, isAuthenticated: false, error: action.payload };
case USER_LOGOUT:
return { loading: false, isAuthenticated: false, user: null };
default:
return state;
}
};
This is my user action:
export const login = (email, password) => async dispatch => {
try {
dispatch({
type: USER_LOGIN_REQUEST,
});
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const { data } = await axios.post(
'/api/v1/users/login',
{ email, password },
config
);
dispatch({
type: USER_LOGIN_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: USER_LOGIN_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
This is my login screen:
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const redirect = location.search ? location.search.split('=')[1] : '/';
const dispatch = useDispatch();
const userLogin = useSelector(state => state.userLogin);
const { loading, error, isAuthenticated } = userLogin;
console.log(isAuthenticated);
useEffect(() => {
if (isAuthenticated) {
history.push(redirect);
}
if (error) {
console.log(error);
}
}, [isAuthenticated, history, redirect, error]);
const submitHandler = e => {
e.preventDefault();
dispatch(login(email, password));
};
I stuck here about 2 days trying to complete this. Please someone help me :)
As you are setting the jwt token in a cookie, you can read it from there when making a request. You can send the cookie automatically with axios by adding withCredentials: true in your axios config object, like:
axios({
withCredentials: true
})
On the server side, you can get the jwt cookie value by looking it on the req.cookies object, like:
let token;
if (req.cookies.jwt) {
token = req.cookies.jwt;
}

Why is Chrome not setting cookie in production

I have an app in production, that sometimes sets a cookie in Chrome, and sometimes doesnt. Im using nodejs on backend. This doesnt happen in firefox, only Chrome. Am I doing something wrong?
Recently I added
this.app.use(cors({ origin: true, credentials: true }));
but it didnt help at all.
Code for setting cookie:
exports.oauthLogin = async (req, res) => {
if (!req.query.token) {
return res.status(400).send({ error: "No token provided" });
}
const settings = await AppSettings.findOne();
const response = await checkToken({
token: req.query.token,
serverUrl: settings.oauthServerUrl,
clientId: settings.oauthClientId,
clientSecret: settings.oauthClientSecret,
});
if (response.error) {
return res.redirect("/");
}
const { _id, displayName, email } = response.user;
let user = await User.findOne({ email: email });
if (!user) {
user = new User({
displayName,
email,
oauthId: _id,
accessLevel: "User",
});
await user.save();
}
const jwtUserData = {
userId: user._id,
userAccessLevel: user.accessLevel,
};
const token = jwt.sign(jwtUserData, process.env.JWT_SECRET);
res.cookie("token", token);
return res.redirect(req.query.redirectUrl ? req.query.redirectUrl : "/");
};

Microservices and refresh tokens NodeJS

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.

Resources