Now I'm doing an authentication project to learn how to integrate with the FE and GraphQL to store the refresh token inside the cookies and use the access token to get the information. Unfortunately I was unable to store the refresh token after I clicked the login button and the cors error even i'm following all the details and steps from the official website. Thank you everyone for being attention on it
Server Code
async function startApolloServer() {
const app = express();
app.use(cors({
origin:'*',
credentials:true, //access-control-allow-credentials:true
}))
const httpServer = http.createServer(app);
app.use(cookiesParser());
app.post("/refresh_token", async (req: Request, res: Response) => {
console.log(req.cookies);
const token = req.cookies.jid;
if (!token) {
return res.send({ status: false, accessToken: "" });
}
let payload: any = null;
try {
payload = verify(token, process.env.SECRET_KEY as string);
} catch (error) {
return res.send({ status: false, accessToken: "" });
}
// token is valid, find user and send back accessToken
const user: any = await AppDataSource.manager.getRepository(User).findOne({
where: {
id: payload.userId,
},
});
if (!user) {
return res.send({ status: false, accessToken: "" });
}
if (user.tokenVersion !== payload.tokenVersion) {
return res.send({ status: false, accessToken: "" });
}
sendRefreshToken(res, createRefreshToken(user));
return res.send({ status: true, accessToken: createAccessToken(user) });
});
const schema = await buildSchema({
resolvers: [UserQuery, UserMutation],
});
await AppDataSource.initialize();
const server = new ApolloServer({
schema,
context: ({ req, res }) => {
return { req, res };
},
csrfPrevention: true,
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});
await server.start();
server.applyMiddleware({ app });
await new Promise<void>((resolve) =>
httpServer.listen({ port: 4000 }, resolve)
);
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`);
}
Frontend Code
const client = new ApolloClient({
uri: "http://localhost:4000/graphql",
cache: new InMemoryCache(),
credentials:'include'
});
Error
Error Image
Remove * and use
app.use(cors())
If you want to allow to access backend can be accessed from anywhere. Or can use
var corsOptions = {
origin: 'http://example.com',
}
If you allow any specific domain. For more options see the documentation.
Related
I am trying to authenticate using google with passport.
Frontend requesting code:
export const googleLoginReq = async () => {
try {
let res = await axios.get(`${apiURL}/auth/google`, { withCredentials: true });
console.log(res);
if(res){
localStorage.setItem("jwt", JSON.stringify(res.token));
}
return res.data;
} catch (error) {
console.log(error);
}
};
server config
const googleAuthSignIn = async (req, res, next) =>{
const token = jwt.sign(
{ _id: req.user._id, role: req.user.userRole },
JWT_SECRET
);
const encode = jwt.verify(token, JWT_SECRET);
res.json({
token: token,
user: encode,
});
}
router.get("/google", passport.authenticate("google", { session: false, scope:
['openid', 'profile', 'email'] }));
router.get("/google/callback", passport.authenticate("google", { session: false }), googleAuthSignIn);
with the above codes, I am getting redirected to the backend URL rather the frontend URL.
So I have to do redirect while returning from googleAuthSignIn middleWare and I am not getting back to the requested call in frontend.
And I need to send token to the client and set that in localStorage but since I am not coming back to the function which called the google authentication API. I am not not able set.
BackendURl: http://localhost:8000/auth/google/callback?code=4%2F0AWtgzh4-kKjJWJG0an8knUu7M4M2G1-lQRH4cwpPu65BgsQEdyg-D7VMnwlbQAfGBMKw_w&scope=email+profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&authuser=0&prompt=consent
frontendUrl: httP://localhost:3000
I am trying to intercept a request from apollo client to apollo server and return either a different typeDef field or an undefined one without the client resulting in an error.
This is to check the users refresh token if there access token is expired, and send a new access token instead of the nomral query they were expecting.
Here is my code:
Server:
const server = new ApolloServer({
typeDefs,
resolvers,
cors: {
origin: "http://localhost:3000",
credentials: true,
},
context: async ({ req, res }) => {
let isAuthenticated = false;
// get the user token from the headers
const authorization = req.get("Authorization");
if (authorization) {
let token = req.headers.authorization.replace(/\(/g, "");
let verifysomething = jwt.verify(
token,
process.env.JWT_SECRET,
(err, decoded) => {
console.log("access decoded");
console.log(decoded);
if (err) {
return err;
}
return decoded;
}
);
if (verifysomething.message == "jwt expired") {
let refreshToken = req.headers.cookie.replace(/^[^=]+=/, "");
let verifyRefresh = jwt.verify(
refreshToken,
process.env.JWT_SECRET,
(err, decoded) => {
if (err) return err;
const accessToken = jwt.sign(
{ userId: decoded.userId },
process.env.JWT_SECRET,
{
expiresIn: "20s",
}
);
isAuthenticated = "newAccess";
return accessToken;
}
);
//end return if now access token but has refresh token
return { isAuthenticated, res, verifyRefresh };
}
let user = await User.findOne({ _id: verifysomething.userId });
if (user) {
isAuthenticated = true;
return {
isAuthenticated,
user,
};
}
}
return { isAuthenticated, res };
},
});
Resolvers:
const resolvers = {
Query: {
getCartItems: authenticate(
async (_, { id }) =>
await cartItem.find({ user: id }).populate("user").populate("item")
),
}}
Authenticate:
function authenticate(resolver) {
return function (root, args, context, info) {
if (context.isAuthenticated == true) {
return resolver(root, args, context, info);
}
else if ((isAuthenticated = "newAccess")) {
return context.verifyRefresh;
}
throw new Error(`Access Denied!`);
};
}
To clarify, the issue is that a request going to getCartItems will be expecting a retun like:
type CartItemPayload {
item: Item
user: User
}
but if their access token is expired I would like to just send them a new one first instead.
Now one way I thought to solve this was by adding a new mutation query for refresh token and on each request from the client I can decode the access token jwt to see if it is expired and if so then I make a request to the refreshToken mutation instead of the getCartItems query for example, and that way I would be able to get the expected typeDef return and easily control the data I am receiving.
The problem I see with that is this just feels like a problem that is supposed to be solved on the serverside. I am new to apollo and graphql and am not using an express server for middleware nor am I using redux for middleware on the client side and not sure if what I am trying to achieve is possible this way.
I am open to any solutions or suggestions for this problem, thanks.
I'm trying to create a platform, once I log in it, creates a token and store it in the cookie. I have successfully been able to create a route that stores the cookie using node js(I could see it saved in postman). But once I try to use the route in my flutter app, it seems the the token doesn't save anywhere.
How do I save the cookie and use it to validate if the user should be logged in or should log in again
Login.dart
Future<void> _signIn(email, password) async {
try {
setState(() {
LoadingWidget.show(context);
bool _absorbme = true;
});
var url = "http://192.168.8.23:3000/route/login"; // iOS
final http.Response response = await http.post(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'email': email.toLowerCase(),
'password': password,
}),
);
SharedPreferences prefs = await SharedPreferences.getInstance();
var parse = jsonDecode(response.body);
await prefs.setString('msg', parse["msg"]);
await prefs.setString('success', parse["success"]);
String? msg = prefs.getString("msg");
} finally {
setState(() {
LoadingWidget.hide(context);
bool _absorbme = false;
});
}
}
My Route.js file
const express = require('express');
const router = express.Router();
const Users = require('./model/model')
const passport = require('passport')
const bcrypt = require("bcrypt");
const { createTokens, validateToken, authenticateToken } = require("./JWT");
const Server = require('http-proxy');
router.get('/', (req, res)=>{
res.send('Hello world')
})
router.post("/signup", async (req, res) => {
const { username, email, password } = req.body;
let user = await Users.findOne({ email });
if (user){
return res.status(400).json({success: 'false', msg: "Email Already Exist"});
}
bcrypt.hash(password, 10).then((hash) => {
Users.create({
username: username,
password: hash,
email:email,
})
.then(() => {
res.json({success: 'true', msg: 'login Successfully'});
})
.catch((err) => {
if (err) {
res.status(400).json({success: 'false', msg: 'Failed to save'});
}
});
});
});
router.post("/login", async (req, res) => {
const { email, password } = req.body;
let user = await Users.findOne({ email });
if (!user) {
return res.status(400).json({success: 'false', msg: 'Authentication Failed, User not found'});
}
const dbPassword = user.password;
bcrypt.compare(password, dbPassword).then((match) => {
if (!match) {
res
.status(400)
.json({success: 'false', msg: 'Authentication failed, wrong password'});
} else {
const accessToken = createTokens(user);
var token = res.cookie("access-token", accessToken, {
maxAge: 60 * 60 * 24 * 30 * 1000,
httpOnly: true,
});
res
.status(200)
.json({success: 'true', msg: 'Successfully logged In'});
}
});
});
router.get("/profile", validateToken, async (req, res) => {
let user = await Users.findOne({email: req.decoded.email});
return res.json({
email: user.email,
balance: user.balance,
name: user.username
})
router.get("/logout", validateToken, (req, res) => {
return res
.clearCookie("access-token")
.status(200)
.json({success: "true" , msg: "Successfully logged out" });
});
router.get("/authenticate" ,authenticateToken, (req,res)=>{
})
module.exports = router;
enter image description here
Why not use JWT it can help you manage your users for the numbers of hours you specified
Create authorize endpoint on your api to refresh token when expired.
In dart side, create an authenticated client singleton.
class AuthenticatedClient extends http.BaseClient {
factory AuthenticatedClient() => _instance;
AuthenticatedClient._();
static final _instance = AuthenticatedClient._();
int? expiresAt;
String? token;
final _client = http.Client();
void setParams(http.Response res) {
final response = jsonDecode(res.body);
expiresAt = response['expiresAt'];
token = response['token'];
}
Future<void> authorize() async {
// Send a request to refresh token, update token and expiresAt
// accordingly. Note that, you can't use _client to send request, use
// htt.put, http.post.
// Ex:
//
// final response = await http.put(
// Uri.parse('https://myapi.com/authorize'),
// headers: {
// 'Content-Type': 'application/json',
// },
// body: jsonEncode({
// 'email': <email>,
// 'password': <password>,
// }),
// );
//
// setParams(response);
}
#override
Future<http.StreamedResponse> send(http.BaseRequest request) async {
// Ensure you set params for the first time.
assert(expiresAt != null);
if (expiresAt! < DateTime.now().millisecondsSinceEpoch) {
await authorize();
}
request.headers['Authorization'] = 'Bearer $token';
return _client.send(request);
}
}
On your api, generate a token on login or signup request and send token to client. For this example, response format is that:
{
'token': <generated token>,
'expiresAt': <timestamp>,
}
When you got response, call setParams method of AuthenticatedClient and set request params. I didn't add code for error handling to the example, don't forget to add.
Use AuthenticatedClient like this:
AuthenticatedClient().get(Uri.parse('https://myapi/profile'));
Before talking about issue, you need some background knowledge about my application.
I use node.js, express.js and mongoDB (with mongoose).
I was making a basic login function to do that I choose JWT for token and passport.js for authentication. and login flow is like below
Issue access token(expires in 10mins) and refresh token (expires in 2 weeks)
Store both token in cookies and store refresh token in DB too
Make a strategy for authentication.
Use strategy when you go to '/current' page.
Below is my code:
user.js:
router.post('/login', (req, res) => {
const { userid, password } = req.body;
// find email
User.findOne({ userid })
.then(user => {
if (!user) {
return res.json({
loginSuccess: false,
message: "Auth failed, ID not found"
});
}
// compare password
bcrypt.compare(password, user.password)
.then(isMatch => {
if (isMatch) {
const accessPayload = {
id: user.id,
userid: user.userid,
role: user.role
};
// console.log(accessPayload.id);
const refreshPayload = {
id: user.id
}
jwt.sign(accessPayload, JWT_ACCESS_SECRET, { expiresIn: JWT_ACCESS_EXPIRATION_TIME}, (err, token) => {
const accessToken = 'Bearer ' + token
const accessT = token
jwt.sign(refreshPayload, JWT_REFRESH_SECRET, { expiresIn: JWT_REFRESH_EXPIRATION_TIME }, (err, refreshT) => {
//note that maxAge is in milliseconds
res.cookie('accessToken', accessT, { httpOnly: true }) //secure: true
res.cookie('refreshToken', refreshT, { httpOnly: true })
res.json({
success: true,
refreshToken: 'Bearer ' + refreshT,
accessToken: accessToken
})
User.saveRefreshToken(refreshT)
//이거 모듈화 왜앙돼
});
});
} else {
return res.json({ loginSuccess: false, message: "Wrong password" });
}
});
});
});
router.get('/current',passport.authenticate('custom', { session: false }), (req, res) => {
res.json({
id: req.user.id,
userid: req.user.userid,
role: req.user.role
// id: req.user.id,
});
})'
and my strategy(custom) is like below
require('dotenv').config();
const { auth } = require('../middleware/auth');
const { append } = require('express/lib/response');
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const passportCustom = require('passport-custom');
const CustomStrategy = passportCustom.Strategy;
// const res = require('express/lib/response');
const jwt = require('jsonwebtoken');
const mongoose = require('mongoose');
const User = mongoose.model('User');
const { verifyToken } = require('../utils/jwt');
module.exports = custom => {
custom.use('custom', new CustomStrategy(
function (req, done) {
const accessToken = req.cookies['accessToken'];
const refreshToken = req.cookies['refreshToken'];
//when both token are valid
if (
req && verifyToken('access', accessToken)
&& verifyToken('refresh', refreshToken)
) {
const verifyAccessToken = verifyToken('access', accessToken);
const user = {
id: verifyAccessToken.id,
userid: verifyAccessToken.userid,
role: verifyAccessToken.role
};
console.log(user)
return done(null, user);
}
// when refresh token is valid but access token expired
else if (
req && !verifyToken('access', accessToken)
&& verifyToken('refresh', refreshToken)
) {
console.log('you need new access token')
const refreshTokenInfo = verifyToken('refresh', refreshToken);
User.findById({ _id: refreshTokenInfo.id }, (err, user) => {
const Payload = {
id: user.id,
userid: user.userid,
role: user.role
}
console.log('old access token : ' +req.cookies['accessToken'])
jwt.sign(Payload, JWT_ACCESS_SECRET, { expiresIn: JWT_ACCESS_EXPIRATION_TIME }, (err, accessToken) => {
if (err) {
console.log(err)
}
console.log('new accesstoken : ' + accessToken)
});
return done(null, true)
})
return done(null, true)
}
}
))
}
The problem is at the case of ' // when refresh token is valid but access token expired'
When accessToken is expired but refresh one is valid I want to make new access token via information from DB(access DB when refresh token is correct) and set in cookie before done() However, I couldn't set cookie because there is no response object in parameter :/.
As far as I know, people use cookie to store access token and sometimes refresh it. I wonder how can I refresh my cookie in jwtStrategy and pass it to /current router so it recognize my refreshed access cookie
Thanks for reading, your help will be appreciated.
I am making full stack app and learn from tutorials and videos . I have a problem with GET request to get information about user which is login in the system. I use Postman to check the requests. When I add user with /login , the Postman look user's accesstoken code. I copy his code and paste it in authorization key in headers in Postman and when I change the URL in localhost to /infor to get information about this user and send it. But it say me "Invalid Authentication". I can't find the wrong. I think the problem is in controllers/userCtrl.js in getUser function. Can you help me?
I put the code:
server.js
require('dotenv').config()
const express = require('express')
const mongoose = require('mongoose')
const cors = require('cors')
const fileUpload = require('express-fileupload')
const cookieParser = require('cookie-parser')
const app = express()
app.use(express.json())
app.use(cookieParser())
app.use(cors())
// Use temp files instead of memory for managing the upload process.
app.use(fileUpload({
useTempFiles: true
}))
// Routes
app.use('/user', require('./routes/userRouter'))
// Connect to Mongodb
const URL = process.env.MONGO_URL
mongoose.connect(URL,{
useCreateIndex: true,
useFindAndModify: false,
useNewUrlParser: true,
useUnifiedTopology: true
}, err =>{
if(err) throw err;
console.log('Connected to MongoDB')
})
const PORT = process.env.PORT || 5000
app.listen(PORT, () => {
console.log('Server is running on port', PORT)
})
.env
MONGO_URL = ***********
ACCESS_TOKEN_SECRET = ***********
REFRESH_TOKEN_SECRET = *************
routes/userRouter.js
require('dotenv').config()
const express = require('express')
const mongoose = require('mongoose')
const cors = require('cors')
const fileUpload = require('express-fileupload')
const cookieParser = require('cookie-parser')
const app = express()
app.use(express.json())
app.use(cookieParser())
app.use(cors())
// Use temp files instead of memory for managing the upload process.
app.use(fileUpload({
useTempFiles: true
}))
// Routes
app.use('/user', require('./routes/userRouter'))
// Connect to Mongodb
const URL = process.env.MONGO_URL
mongoose.connect(URL,{
useCreateIndex: true,
useFindAndModify: false,
useNewUrlParser: true,
useUnifiedTopology: true
}, err =>{
if(err) throw err;
console.log('Connected to MongoDB')
})
const PORT = process.env.PORT || 5000
app.listen(PORT, () => {
console.log('Server is running on port', PORT)
})
models/userModel.js
const mongoose = require('mongoose')
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true,
},
role: {
type: Number,
default: 0
},
cart: {
type: Array,
default: []
}
}, {
timestamps: true
})
module.exports = mongoose.model('Users', userSchema)
middleware/auth.js
const jwt = require('jsonwebtoken')
const auth = (req, res, next) => {
try{
const token = req.header("Authorization")
if(!token) return res.status(400).json({ msg: "Invalid Authentication" })
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
if(!err) return res.status(400).json({msg: "Invalid Authentication" })
req.user = user
next()
})
} catch (err) {
return res.status(500).json({msg: err.message})
}
}
module.exports = auth
controllers/userCtrl.js
const Users = require('../models/userModel')
const bcrypt = require('bcrypt')
const jwt = require('jsonwebtoken')
const userCtrl = {
register: async (req, res) => { // async before a function means one simple thing: a function always returns a promise.
try{
const { name, email, password } = req.body
const user = await Users.findOne({ email }) // wait until the promise resolves
if(user) return res.status(400).json({msg: "The email already exists"})
if(password.length < 6)
return res.status(400).json({msg: "Password is at least 6 characteres long."})
//Password encryption
const passwordHash = await bcrypt.hash(password, 10)
const newUser = new Users({
name, email, password: passwordHash
})
// save mongodb
await newUser.save()
//then create jsonwebtoken to authentication
const accesstoken = createAccessToken({ id: newUser._id })
const refreshtoken = createRefreshToken({ id: newUser._id })
res.cookie('refreshtoken', refreshtoken, {
httpOnly: true,
path: '/user/refresh_token'
});
res.json({accesstoken})
} catch(err){
return res.status(500).json({msg: err.message})
}
},
login: async (req, res) => {
try{
const {email, password} = req.body;
const user = await Users.findOne({email})
if(!user) return res.status(400).json({msg: "User does not exist."})
const isMatch = await bcrypt.compare(password, user.password)
if(!isMatch) return res.status(400).json({msg: "Incorrect password"})
// if login success, create access token and refresh token
const accesstoken = createAccessToken({ id: user._id })
const refreshtoken = createRefreshToken({ id: user._id })
res.cookie('refreshtoken', refreshtoken, {
httpOnly: true,
path: '/user/refresh_token'
});
res.json({accesstoken})
} catch(err){
return res.status(500).json({msg: err.message})
}
},
logout: async (req, res)=> {
try{
res.clearCookie('refreshtoken', {path: '/user/refresh_token'})
return res.json({msg: "Logged out"})
}catch(err){
return res.status(500).json({msg: err.message})
}
},
refreshToken: (req, res) => {
try{
const rftoken = req.cookies.refreshtoken
if(!rftoken) return res.status(400).json({msg: "Please login or Register"})
jwt.verify(rftoken, process.env.REFRESH_TOKEN_SECRET, (err, user) => {
if(err) return res.status(400).json({msg: "Please login or Register"})
const accesstoken = createAccessToken({id: user.id})
res.json({ accesstoken })
})
}catch (err) {
return res.status(500).json({msg: err.message})
}
},
getUser: async (req, res) => { // problem
try{
const user = await (await Users.findById(req.user.id)).isSelected('-password')
if(!user) return res.status(400).json({ msg: "Useer does not exist."})
res.json(req.user)
}catch (err) {
return res.status(500).json({msg: err.message})
}
}
}
const createAccessToken = (user) => {
return jwt.sign(user, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '1d' })
}
const createRefreshToken = (user) => {
return jwt.sign(user, process.env.REFRESH_TOKEN_SECRET, { expiresIn: '7d' })
}
module.exports = userCtrl
For your middle ware for getting the token (auth function)
const { authorization } = req.headers
if (!authorization) {
console.log('[No Authorization Code]');
return res.status(401).send({ message: 'Unauthorized' });
}
if (!authorization.startsWith('Bearer')) {
console.log('[Authorization need to start with Bearer]')
return res.status(401).send({ message: 'Unauthorized' });
}
const split = authorization.split('Bearer ')
if (split.length !== 2) {
console.log('[Invalid Authorization Param')
return res.status(401).send({ message: 'Unauthorized' });
}
const token = split[1] //this is your token to use with jwt.verify
When you sending the token in postman, select Bearer Token
When you start creating your frontend, the codes should be equivalent to the following fetch request
fetch('/api/path', { method: 'GET', headers: { "Authorization": `Bearer ${token}`}}).(res => res.json())
May change method to your desire method (e.g get or post), and the token will be the the jwt token