Express doesn't set cookies with httpOnly flag - javascript

I have my back-end Express.js server that has sign in function. After user sign in, he gets 2 tokens - access token and refresh token. What I want to do, is to make return from server refresh token as httpOnly cookie.
Here is a peace of code of this function:
const { refreshToken, accessToken } = await jwtService.updateTokens({
userId: client.id, username: client.username
}, { transaction })
logger.info(`Client ${client.email} has been successfully signed in!`)
await transaction.commit()
return res
.status(200)
.cookie("refreshToken", JSON.stringify(refreshToken), { httpOnly: true, secure: false })
.json({ accessToken, reopening: reopening ? client.username : null })
Basically, browser just doesn't set this cookie as httpOnly and doesn't set it at all, actually. So, I was trying to ping this endpoint with postman, and it works:
In reponse body I have access token and in httpOnly cookie I have this refresh token.
So, the problem is, why browser doesn't save it? I have found a couple of solutions and all of them were about cors and axios credentials. I use 2 express servers - 1 is for normal back-end and 1 is for front-end.
Here is how "path" from front-end to back-end looks like:
Sign in function that send request to front-end express server:
const api = axios.create({
baseURL: apiUrl,
headers: {
'Content-Type': 'application/json'
}
})
export const signIn = async payload => {
try {
const { data } = await api.post('s-i', payload)
return data
} catch (e) {
return e.response.data
}
}
Front-end express server sends request to actual back-end:
const api = axios.create({
baseURL: process.env.NODE_ENV === "development" ? process.env.PNB_API_DEV : process.env.PNB_API_PROD,
})
const router = Router()
router.post('/s-i', async (req, res) => {
try {
const { data } = await api.post('/sign-in', req.body)
res.json(data)
} catch (e) {
return res.status(e.response.status).json(e.response.data)
}
});
And then that function that was at the very begging.
So - the question is - how to make browser save those httpOnly cookies? If it's really about credentials or cors where should I put those settings?
PS
Back-end port - 3001 and front-end port - 8010.

Related

request to login to a node server, using react

Here is the situation:
I have a database which contains a user and password registered.
My assignment, for now, is to create a login form, and login with a registered uname and pw.
Uname and pw are registered in the server/database already.
ps: I did not create the server nor database.
Node server code
import express from 'express';
import cors from 'cors';
import http from 'http';
import { Sequelize } from 'sequelize';
import { Data } from './database';
import { router } from './routes/Router';
import { initialData } from './database/someData';
const closeServer = async (
server: http.Server,
sequelize: Sequelize,
signal?: string
) => {
server.close();
await sequelize.close();
process.exit();
};
const runServer = async (): Promise<void> => {
const PORT = process.env.PORT || 8082;
const app = express();
const sequelize = Data.init();
app.use(
cors({
credentials: true,
origin: 'http://localhost:3000',
})
);
app.use('/api', router);
const server = app.listen(PORT, () => {
console.log(`Starting server at ${PORT}`);
});
try {
await sequelize.authenticate();
await sequelize.sync({
force: process.env.SERVER === 'reset',
});
if (process.env.SERVER === 'reset') await initialData();
} catch (e) {
closeServer(server, sequelize);
throw e;
}
};
runServer()
.then(() => {
console.log('Run successfully');
})
.catch((ex: Error) => {
console.log('Unable to run:', ex);
});
I need help on what is that I have to do.
When I input username and pw, on the form, what are the methods to use for sending the info?
And then, when the info reaches the server, i think the username and pw need to be validated with jwt, and then check if the user and pw exists. how do i do that?
What i have understood so far is that i gotta use axios to send info to server, but thats it.
Do i need to use jwt for the login?
What is the normal flow for this kind of mechanism?
I am using react as a framework.
So there are quite few steps here.
First you have to create endpoint on your backend server for issuing jwt tokens. Jwt tokens can be used as a pass for user to login. So in your router you would add something like this:
router.post('/login', (req, res)=> {
const username = req.body.username
const password = req.body.password
// Then you make db call to verify username and password are correct.
if Credentials are valid, you would issue jwt token
jwt.sign({
// here you can save extra information of user. Also remember this information must be public since anyone can see it. Do not put user password here
email: 'email',
userId: 'id',
}, "secret")
})
After this, you need some kind of middleware on backend, so that on each user request, you check and verify this jwt token which is sent from react application. For example you could write isAuth middleware:
const jwt =require("jsonwebtoken");
export const isAuth= (req, res, next) => {
try {
// here we attach request in auth header, with Bearer "jwt token" format. So we extract jwt token and verify it
const authHeader = req.get("Authorization");
if (!authHeader) {
return res.status(401).json({ message: "no token" });
}
const token = authHeader.split(" ")[1];
let decodedToken;
decodedToken = jwt.verify(token, "secret");
if (!decodedToken) {
return res.status(401).json({ message: "Wrong token" });
}
req.userId = decodedToken.userId;
next();
} catch (err) {
console.error(err);
return res.status(401).json({ message: err });
}
};
Now you would be able to have backend endpoints like this:
// This is how you would require login on some routes
router.post("/getMyPrivateInfo", isAuth, QueryPrivatInfo)
Now on React side, you would make request for login like this:
axios.post("/login", {
username: '1',
password: "2"
})
This would return jwt token, now you would save this token in local storage.
After its saved in local storage and you make request with axios for private info you would do following
axios.post("/getMyPrivateInfo", {any request body info neeeded}, {
headers: {
Authorization: "Bearer jwtTokenFromLocalStorage"
}
})
This is how whole flow will work, hope it makes sense

After Authenticating with google using passport its redirecting to backend URL rather then frontend URL in MERN app

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

Why cookie is not set?

Do you have any idea why cookie is not set in client? It is sent from backend:
func loginEmail(_ req: Request) throws -> Response
{
let response = Response(status: .ok)
let cookie = HTTPCookies.Value(string: "abcdef")
response.cookies["userId2"] = cookie
return response
}
it is visible in browser in Network tab
set-cookie: userId2=abcdef; Path=/; SameSite=Lax
but not on Application
GET is sent to backend. Backend runs on 8080 port, frontend on 3000.
I use axios in a React / Next.js app for calling endpoint:
const login = () => {
axios
.get(`http://localhost:8080/loginEmail`)
.then((res) => {})
.catch((err) => console.error(err));
};
I am using Vapor as backend, and has the following configurations, maybe they matter:
app.middleware.use(CORSMiddleware(configuration: .init(
allowedOrigin: .originBased,
allowedMethods: [.GET, .POST, .PUT, .OPTIONS, .DELETE, .PATCH],
allowedHeaders: [.accept, .authorization, .contentType, .origin, .xRequestedWith, .userAgent, .accessControlAllowOrigin, .init("crossDomain"), .accessControlAllowCredentials, .xRequestedWith]
)))
app.sessions.configuration.cookieName = "userId2"
// Configures cookie value creation.
app.sessions.configuration.cookieFactory = { sessionID in
print("sessionID.string: \(sessionID.string)")
return .init(string: sessionID.string, isSecure: false)
}
app.middleware.use(app.sessions.middleware)

How to retrieve web-token from cookie using express, passport

Can't access token saved to cookies using passport, express and jsonwebtokens.
I'm using passport for authorization and passport-jwt to authentication of web-tokens. I have verified that my server is issuing web-tokens and setting cookies on the browser, but when I attempt to use secure routes it gives me an unauthorized message.
...
// fetching from server
const response = fetch("http://localhost:5000/user/profile");
...
...
app.use(cors({ credentials: true, origin: "http://localhost:3000" }));
app.use(cookieParser("password"));
app.use("/",require("./routes/routes"));
app.use("/user",passport.authenticate("jwt",
{session:false},require("./routes/secure-routes"));
...
...
router.post("/login",async(req,res)=>{
passport.authenticate("login",{session:false},async (err,user)=>{
...
req.login(payload,{session:false},async error=>{
...
const token = jwt.sign(JSON.stringify(payload),"password");
res.cookie("jwt",token,{httpOnly:true});
res.status(200).send({msg:"cookie set!});
}}
})
...
...
const JWTstrategy = require("passport-jwt").Strategy;
passport.use(
new JWTstrategy(
{
jwtFromeRequest: req=>req.cookies.jwt,
secretOrKey: "password"
},
(jwtPayload, done) => {
return done(null,jwtPayload);
}
)
...
The server is definitely setting the cookies on the browser to the webtoken, but for some reason I can't retrieve the token from the GET route. Any help would be greatly appreciated.
You need to include the cookies.
const response = fetch("http://localhost:5000/user/profile", {
credentials: "include"
});

Not able to retrieve the updated token from localStorage in ApolloClient

I'm implementing React Authentication with ApolloGraphQL.
Context:
In signin.js, I'm generating the token when user clicks submit button and set it to the Localstorage.
Then, I'm retrieving the token in App.js to I can pass it to GraphQL, so that, it can be retrieved in in server.js.
Problem:
After user clicks the submit button, I can see the newly generation token in: Developer Tools > Application > Local Storage.
But it returning 'null' for 'client side token' in App.js
When I do the signin again, I'm seeing the previously generated token as the value of 'client side token', which means its not getting the updated token from the LocalStorage.
Obviously, because of this, 'server side token' is null for the first time and returning the previously generated token for the second time signin.
app/client/src/components/signin.js:
handleSubmit = (event, SignIn) => {
event.preventDefault();
SignIn().then(({ data }) => {
localStorage.setItem('token', data.SignIn.token);
this.clearState();
})
}
app/client/src/app.js:
//initiating the ApolloClient
const client = new ApolloClient({
uri: 'http://localhost:4000/graphql',
fetchOptions: {
credentials: 'include'
},
//adding token to the localstorage
request: operation => {
const token = localStorage.getItem('token');
operation.setContext({
headers:{
authorization: token
}
})
console.log(`client side token ${token}`);
},
//catching the most common network error, if any
onError: ({ networkError }) => {
if(networkError){
console.log('Network Error', networkError);
}
}
});
server.js:
const app = express();
app.use(async (req, res, next) => {
const token = req.headers['authorization'];
console.log(`server side token: ${token}`);
next();
});
you need somekind of auth middleware as request in apollo client is only getting called on construction i believe which is why the localStorage has the previous token when you reload the page.
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { ApolloLink, concat } from 'apollo-link';
const httpLink = new HttpLink({ uri: '/graphql' });
const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization to the headers
operation.setContext({
headers: {
authorization: localStorage.getItem('token') || null,
}
});
return forward(operation);
})
const client = new ApolloClient({
link: concat(authMiddleware, httpLink),
});
see https://www.apollographql.com/docs/react/advanced/network-layer/#middleware for more details

Categories

Resources