I love coding on CodeSandbox for client and Repl for server.
I am learning create an auth microservices to handle twitter login recently.
I followed this tutorial
https://www.freecodecamp.org/news/how-to-set-up-twitter-oauth-using-passport-js-and-reactjs-9ffa6f49ef0/
and setup the client on CodeSandbox and Server on Repl
https://codesandbox.io/s/passport-pratice-twitter-p1ql3?file=/src/index.js
_handleSignInClick = () => {
// Authenticate using via passport api in the backend
// Open Twitter login page
// Upon successful login, a cookie session will be stored in the client
//let url = "https://Passport-pratice-twitter.chikarau.repl.co/auth/twitter";
let url = "http://localhost:4000/auth/twitter";
window.open(url, "_self");
};
https://repl.it/#chiKaRau/Passport-pratice-twitter#index.js
app.use(
cookieSession({
name: "session",
keys: [keys.COOKIE_KEY],
maxAge: 24 * 60 * 60 * 100
})
);
// parse cookies
app.use(cookieParser());
// initalize passport
app.use(passport.initialize());
// deserialize cookie from the browser
app.use(passport.session());
// set up cors to allow us to accept requests from our client
app.use(
cors({
//origin: "https://p1ql3.csb.app", // allow to server to accept request from different origin
origin: "http://localhost:3000",
methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
credentials: true // allow session cookie from browser to pass through
})
);
If I test them on localhost, they works perfectly (It displays login successfully)
However, testing them on CodeSandBox and Repl won't work because of req.user is undefined. The passport.session supposes to store the user session/cookie into req as req.user, and it will check whether the req.user exist and send a response back to client.
router.get("/login/success", (req, res) => {
if (req.user) {
res.json({
success: true,
message: "user has successfully authenticated",
user: req.user,
cookies: req.cookies
});
}
});
I also tested on both localhost and browser and set the appropriate domain
Client (PC) and Server (PC) - Working
Client (PC) and Server (REPL) - not Working
Client (CodeSandBox) and Server (PC) - not Working
Client (CodeSandBox) and Server (REPL) - not Working
My Question are why the cookie session is not working on online IDE such as CodeSandBox or Repl?
Is there any solution to get around this and run on CodeSandBox or Repl? If deploy both client and server on a server like heroku or digital ocean, would it gonna works as localhost?
Thanks
Related
I'm generating JWT token on my Nestjs backend which then I try to send with cookie to my frontend React application in order to know which user is logged in.
Problem is that I'm not receiving this cookie in browser, and it's not automatically added to other requests.
I'm sending response inside my service like this:
async login(loginData: UserLoginInterface, res: Response) {
...
return res.status(200).cookie('jwt', token.accessToken, {
secure: false,
domain: 'localhost',
httpOnly: false,
}).json(userResponse);
}
At this point I know the token is generated, it's saved in DB.
But I can't see this cookie, or any other cookie I try in my browser:
Doesn't matter if the httpOnly flag is true or false.
And then, when I try to call action that is restricted only for logged in user, which have the jwt token in request, then Nest is throwing 401 UnauthorizedException
So at this point I know that it's not sent automatically with request as I read in other thread like this:
Why browser is not setting the cookie sent from my node js backend?
But when I make this POST request from Postman.
Then I can see that cookie is sent properly and I can read the JWT token:
Along with headers:
And also it works fine when I call the function that is restricted only to authorized users.
Here is my bootstrap in main.ts:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors({
origin: 'http://localhost:3000'
});
app.getHttpAdapter().getInstance().disable('x-powered-by');
app.use(cookieParser());
await app.listen(process.env.PORT || 3500);
}
bootstrap();
After some time of debugging I found out that it's not the browser that is ignoring properly sent cookie, and in fact it is backend that is not sending the cookie to the browser client.
And the thing was about how the request is being send.
I've found this thread to be useful:
Express doesn't set a cookie
In my case setting flag withCredentials: true in axios was sufficient.
const API = axios.create({
baseURL: 'http://localhost:5000',
withCredentials: true,
});
EDIT
Also, seems like the way I send response also matters, the code above is not sending cookie properly to the browser for some reason, but this works fine:
res.status(200).cookie('jwt', token.accessToken);
return res.json(userResponse);
I am creating a react app and I was adding functionality of registering users.
Everything was successful but I am unable to access Passport User property in socket I used the same code given in socket.io example
const session = require("express-session");
const passport = require("passport");
io.use(wrap(session({ secret: "cats" })));
io.use(wrap(passport.initialize()));
io.use(wrap(passport.session()));
io.use((socket, next) => {
if (socket.request.user) {
next();
} else {
next(new Error("unauthorized"))
}
});
This example works fine if domain is same but when I use CORS I am unable to access the passport property in session.
my react app domain is localhost:3000 and socket server domain is localhost:5000
Assuming that you are using same protocol and same domain but different ports it should still work fine if you setup your client and server with cors flags, e.g
// server-side
const io = new Server(httpServer, {
cors: {
origin: "https://example.com",
allowedHeaders: ["my-custom-header"],
credentials: true
}
});
// client-side
import { io } from "socket.io-client";
const socket = io("https://api.example.com", {
withCredentials: true,
extraHeaders: {
"my-custom-header": "abcd"
}
});
The sample above was taken from socket.io docs: https://socket.io/docs/v4/handling-cors/
However, the above configuration will work only if client/server are sharing the same top level domain and same protocol. e.g. client: https://example.com, server: https://server.example.com
I spent some time to figure out myself why:
client: http://127.0.0.1:3000 does not work with server: https://127.0.0.1:8000, notice the protocol difference.
With cors configurations in place, it works fine if I use http://127.0.0.1:8000 for server.
PS: If you need to use different top domains, be aware of SameSite policy that might be in place for your browser: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
This policy might restrict your cookies to be sent to server.
so... if different protocol or domains, you should make sure that you session cookie has SameSite flag set as 'none', via:
const session = require('express-session');
...
// Session setup
const sessionConfig = {
secret: 'secret', // Session secret
resave: false, //don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
cookie: {
sameSite: 'none',
secure: true,
}
}
const sessionMiddleware = session(sessionConfig);
app.use(sessionMiddleware);
...
io.use(wrap(sessionMiddleware));
both sameSite and secure properties are needed if you are playing with https:// protocol
I'm using Nodejs for my server and Reactjs for my client. In my client, I use axios to perform post/get requests to the server.
During development, everything is working perfectly fine, data is fetched and cookies are set properly. However, when I deploy my server and client to Heroku and Netlify respectively, the cookies are suddenly not set.
Here is my server code:
dotenv.config()
const server = express();
server.use(cors({origin: "https://frontendname.com", credentials: true}));
server.use(express.json());
server.use(express.urlencoded({extended:true}))
server.use(cookieParser())
server.use("/", fetchData)
server.listen(process.env.PORT, ()=>console.log(`Server listening on PORT ${process.env.PORT}`))
mongoose.connect(process.env.MONGO_URI, {useNewUrlParser: true, useUnifiedTopology: true}).then( () => {
console.log("connected to mongoose")
}).catch((error) => console.log(error.message))
My server API code
res.status(201) .cookie("accessToken", accessToken, {domain:"frontendwebsite.com", httpOnly: true, sameSite: 'strict', path: '/', expires: new Date(new Date().getTime() + 60 * 1000 * 4)}) .cookie("refreshToken", refreshToken, {domain:"frontendwebsite.com", httpOnly: true, sameSite: 'strict', path: '/', expires: new Date(new Date().getTime() + 60 * 1000 * 96)}) .json({message: "login verified", username: req.body.username}) .send()
My client API code:
axios.defaults.withCredentials = true
export const loginAuth = async (credentials) => {
return await axios.post("https://backendname.herokuapp.com/loginAuth", credentials).then((res) => {
return res
})
}
I have a strong feeling its due to the domain name that is not correct in the "res.cookie". Since in my development when I'm using localhost for both server and client, it works. My client is hosted on Netlify and server is hosted on Heroku. My client has a custom domain name I got from GoDaddy. I used Netlify DNS on that domain name. Any ideas?
Can you inspect the response coming back from your server (with browser Dev Tools)? Is there any Set-Cookie header / value returning, or is it completely missing?
My guess is the cookie header is there, and being set, but with sameSite set to strict it won't send it to your backend / API. So I think you are right about the domain being incorrect, in res.cookie, you could try that with backendname.herokuapp.com. Since really you want the cookie to be transmitting to / from your backend, especially as it is httpOnly, it will never be used by your frontend / client.
This might seem like a redundant question, but please hear me out first:
I'm working with a React Frontend and a Node Backend. I'm using JWT to deal with user authentication. Right now, I'm having trouble actually working with the JWT and performing the authentication. Here's where I'm stuck:
~ I try setting the token as an http cookie in my backend. If i work with postman, I see the token being set. However, when I use req.cookies.token to try and receive the token cookie to perform validation in the backend, I get an undefined value. Am I supposed to be sending the cookie from the frontend to the backend somehow? I feel like this is the part that I am missing.
Please advise!
SO I can give you an alternative solution to handling session making use of express-session and connect-mongodb-session this has tend to been the popular and somewhat secure solution for server session handling
Firstly you will need the following packages
npm i express-session connect-mongodb-session or yarn add express-session connect-mongodb-session
Now that we have packages that we need to setup our mongoStore and express-session middleware:
//Code in server.js/index.js (Depending on your server entry point)
import expressSession from "express-session";
import MongoDBStore from "connect-mongodb-session";
import cors from "cors";
const mongoStore = MongoDBStore(expressSession);
const store = new mongoStore({
collection: "userSessions",
uri: process.env.mongoURI,
expires: 1000,
});
app.use(
expressSession({
name: "SESS_NAME",
secret: "SESS_SECRET",
store: store,
saveUninitialized: false,
resave: false,
cookie: {
sameSite: false,
secure: process.env.NODE_ENV === "production",
maxAge: 1000,
httpOnly: true,
},
})
);
Now the session middleware is ready but now you have to setup cors to accept your ReactApp so to pass down the cookie and have it set in there by server
//Still you index.js/server.js (Server entry point)
app.use(
cors({
origin: "http://localhost:3000",
methods: ["POST", "PUT", "GET", "OPTIONS", "HEAD"],
credentials: true,
})
);
Now our middlewares are all setup now lets look at your login route
router.post('/api/login', (req, res)=>{
//Do all your logic and now below is how you would send down the cooki
//Note that "user" is the retrieved user when you were validating in logic
// So now you want to add user info to cookie so to validate in future
const sessionUser = {
id: user._id,
username: user.username,
email: user.email,
};
//Saving the info req session and this will automatically save in your mongoDB as configured up in sever.js(Server entry point)
request.session.user = sessionUser;
//Now we send down the session cookie to client
response.send(request.session.sessionID);
})
Now our server is ready but now we have to fix how we make request in client so that this flow can work 100%:
Code below: React App where you handling logging in
//So you will have all your form logic and validation and below
//You will have a function that will send request to server
const login = () => {
const data = new FormData();
data.append("username", username);
data.append("password", password);
axios.post("http://localhost:5000/api/user-login", data, {
withCredentials: true, // Now this is was the missing piece in the client side
});
};
Now with all this you have now server sessions cookies as httpOnly
I work with app, that already has its own infrastructure. The task is to integrate session-cookie mechanism. I spent a lot of time to understand why cookies doesn’t set on client side.
I. Briefly.
App settings:
Server: NodeJS
Port: 8081
Client: VueJS
Port: 8088
I use module "express-session" to initialize session mechanism on server side and send cookies to client. Client hasn’t set cookies.
II. Details:
Server’s root file is index.js.
I do the following in it:
Plug in express module:
const express = require('express')
Plug in cors module:
const cors = require('cors')
Add cors settings:
app.use(cors({
origin: 'http://localhost:8088',
credentials: true
}))
Then I initialize session in user.js file and receive client’s connects:
Plug in express-session module:
const session = require('express-session')
Plug in routing by express.Router():
const router = express.Router()
Add session settings:
const EIGHT_HOURS = 1000 * 60 * 60 * 2
const {
SESS_NAME = 'sid',
SESS_LIFETIME = EIGHT_HOURS,
SESS_SECRET = 'test',
NODE_ENV = 'development'
} = process.env
const IN_PROD = NODE_ENV === 'production'
Initialize session:
router.use(session({
name: SESS_NAME,
resave: false,
saveUninitialized: false,
secret: SESS_SECRET,
cookie: {
maxAge: SESS_LIFETIME,
sameSite: false,
// Must have HTTPS to work 'secret:true'
secure: IN_PROD
}
}))
Receive client queries by router.post()
App client side consists of a lot of files. Client send data to NodeJS server by Axios module.
I read several articles by this theme and I guess that server side settings, which I made, are enough for work session-cookie mechanism. That means, that problem is on Vue side.
What I made:
I set in all files, where Axios send data to server, parameter withCredentials in true value (withCredentials: true) to pass CORS restrictions. This didn’t help
App in production has other URLs for accessing the production NodeJS server. I set develop NodeJS server URL in all client side files. This didn’t help
Read this article: Vue forum. From this article I understood, that need to solve this problem by axios.interceptors (StackOverFlow forum). I supposed that if this setting set on one of the client’s side pages, may be cookies should work at least on this page. This didn’t help.
Tried to set setting like this:
axios.defaults.withCredentials = true
And that:
axios.interceptors.request.use( function (config) {
console.log('Main interceptor success')
config.withCredentials = true;
return config;
},
function(error) {
// Do something with request error
console.log('Main interceptor error')
return Promise.reject(error);
}
)
This didn’t help
Please, tell me in which direction I should move? Is that right, that on client side on absolutely all pages must be axios.defaults.withCredentials = true setting to initialize cookies mechanism? What details I miss? If I set session-cookies from scratch the mechanism works.
I resolve this issue. I need to look for cookie storage in another browser place:
Chrome server cookie storage