NextJS httpsOnly cookie JWT Auth - express

I'm trying to use JWT authentication for my app and save the token as an httpOnly cookie to use for subsequent requests. The requests work in Insomnia, setting and then using the cookie to login, but don't work in my NextJS frontend.
My frontend is at localhost:3000 and my backend is at localhost:3001 but I have cors set.
app.use(
cors({
origin: "http://localhost:3000",
optionsSuccessStatus: 200,
credentials: true,
})
);
app.use(cookieParser());
My request for data once logged in looks like this:
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
body: JSON.stringify(data),
};
fetch(url, options)
.then((res) => res.json())
.then((res) => {
// Do stuff
});
On the server I set the token using
res.cookie("token", token, {
httpOnly: true,
secure: true,
});
but when I console log req.cookies.token on the server for the data request I get undefined. Is there something I'm doing wrong that's preventing the cookie from getting set/sent?
Or is this related to localhost not setting cookies? I thought that didn't apply for httpOnly cookies. Thanks!

You're setting the secure parameter on your cookie. This means that the browser will drop the cookie if the connection to your backend is not over SSL.
You would have to configure your backend to use SSL (probably with some self-signed certificates) or set the secure parameter on your cookie to false.

Related

Axios NextJS to Express CORS blocked

I'm calling a my express server in NextJS like this:
const { data } = await axios.post(
url,
body,
{
withCredentials: true,
}
);
and in the server I have this configuration:
app.use(
cors({
origin: "*",
credentials: true,
})
);
When I make requests using Nextjs in any browser I get CORS error no allow credentials.
I call the same endpoint in Insomnia and don't get any issue

HttpOnly cookie appears in response header but is not being saved to the browser

I recently built a simple real-time chat application with Nextjs on the frontend and Express on the backend. The frontend is deployed on vercel while the backend is deployed on heroku. When a user logs into the app, the backend generates a jwt token which is then sent via an HttpOnly cookie back to the frontend. Here is the code for said response:
const authenticate = async (req, res, next) => {
userService
.authenticate(req)
.then((user) => {
const { token, ...userInfo } = user;
res
.setHeader(
"Set-Cookie",
cookie.serialize("token", token, {
httpOnly: true,
secure: process.env.NODE_ENV !== "development",
maxAge: 60 * 60 * 24,
sameSite: "none",
path: "/",
})
)
.status(200)
.json(userInfo);
})
.catch(next);
};
After authentication, each subsequent request to the backend is supposed to send the token to ensure the user is logged in. For example, this is the request sent to the server to get a chat between the logged in user and another user.
const getChat = async (id) => {
const identification = id;
const response = await axios.get(
`<SERVER_URL>/chats/chat/${identification}`,
{ withCredentials: true }
);
return response;
};
In development when on localhost:3000 for the frontend and localhost:4000 for the backend, everything works fine. However, when I deployed the frontend to vercel and the backend to heroku, the browser simply refuses to save the cookie! The jwt token appears in the response header after sending the authentication request, but it isn't saved to the browser. I have tried absolutely everything I can think of, including changing the cookie parameters, but I can't get it to work. I am pretty sure I have cors properly configured on the backend as well, along with the cookie-parser module:
const cors = require("cors");
const cookieParser = require("cookie-parser");
app.use(
cors({
origin: "<CLIENT_URL>",
credentials: true,
})
app.use(cookieParser());
Thanks for taking the time to read this, any help would be greatly appreciated! And my apologies if I have not elaborated enough, this is my first post here and I'm still trying to learn the proper etiquette of the site!
HttpOnly can not read or write on client-side but when the first HttpOnly send through a request other request on the same origin can access the coockies in backend but you should request in Next.js like this.
Next.js using fetch :
const req = await fetch("http://localhost:7000/api/auth/login", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Credentials": true,
},
body: JSON.stringify({
email: formData.get("email"),
password: formData.get("password"),
}),
});
const data = await req.json();
then in backend you can read the coockie through coockie-parser
server.js:
const cookieParser = require("cookie-parser");
app.use(coockieParser());
route.post('/login',(req,res) => {
if(user){
res
.cookie("access_token", newToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production" ? true : false,
})
.status(200)
.json({ ok: true, payload: data });
}
})
Now you can read this cookie in other routes but sure about the expiration time.

Problems to get a refresh token using vue, nuxt and keycloak

I'm doing a project with vue, nuxt and keycloak as server for token, axios as http client and #nuxtjs/auth-next module for keycloak access.
I'm using a public client so I don't have a secret key which is the most recommended.
The part of getting the token and talking to the backend is working.
But as it is a public client it has no refresh token.
Searching the internet, a recommendation would be to post from time to time to the keycloak /token endpoint, passing the current token, to fetch a new token.
To perform this post, it doesn't work to pass json, having to pass application/x-www-form-urlencoded.
But it generates an error saying that the parameter was not passed.
On the internet they recommended passing it as url string, but then it generates an error on the keycloak server, as a parameter that is too long, because of the current token that is passed.
Below is the code used to try to fetch a new token.
This code is being called on a test-only button.
If anyone can help, I appreciate it.
const token = this.$auth.strategy.token.get()
const header = {
"Content-Type": "application/x-www-form-urlencoded"
}
const body = {
grant_type: "authorization_code",
client_id: "projeto-ui",
code: token
}
this.$axios ( {
url: process.env.tokenUrl,
method: 'post',
data: body,
headers: header
} )
.then( (res) => {
console.log(res);
})
.catch((error) => {
console.log(error);
} );
Good afternoon people.
Below is the solution to the problem:
On the keycloak server:
it was necessary to put false the part of the implicit flow.
it was necessary to add web-origins: http://localhost:3000, to allow CORS origins.
In nuxt.config.js it was necessary to modify the configuration, as below:
auth: {
strategies: {
keycloak: {
scheme: 'oauth2',
...
responseType: 'code',
grantType: 'authorization_code',
codeChallengeMethod: 'S256'
}
}
}

How do I use an HttpOnly jwt cookie on the frontend

How do I use an HttpOnly JWT cookie on the frontend since I can't read from an HttpOnly cookie?
What's the best practice for storing the JWT after grabbing it from the HttpOnly cookie? Do I even need to store it somewhere since I already have the cookie passed from the backend?
I know local storage is not secure.
I don't know if it matters, but I'm serving my Vue app on http://localhost:8080/, and I'm running my backend app on http://localhost:3000 using Node/Express.
httpOnly cookies by design are not accessible by your browser.
You have to include credentials with your request with whatever http client you are using:
fetch.js
// with axios
axios.post(url, formData, {
withCredentials: true
})
// or with fetch
fetch(url, {
credentials: 'include'
});
And inside your express entrypoint:
app.js
const cors = require("cors");
const cookieParser = require("cookie-parser");
const COOKIE_SECRET = "veryLongString";
const corsOptions = {
credentials: true,
... other cors options ...
};
app.use(cookieParser(COOKIE_SECRET));
app.use(cors(corsOptions));
These configurations will automatically pass your httpOnly cookie along with your requests, and you can access them from your server
If the cookie is signed:
const { tokenName } = req.signedCookies;
or else:
const { tokenName } = req.cookies;

Cookies included in request header, but express returns empty cookies (using Amazon Load Balancer)

I'm doing fetch from my frontend to my express backend. But express logs req.cookies as '' (empty). I'm using cookieParser.
Why is express not finding the cookie, even though the browser shows the cookies being sent?
Note: I'm using cookies forwarded by my load balancer, which does the authentication and sends the session over.
Frontend
fetch(`${MY_URL}/logout`, {
credentials: 'include',
})
NodeJS
const cookieParser = require("cookie-parser");
app.use(cookieParser());
app.get("/logout", (req, res, next) => {
console.log(req.headers) // see below
console.log(JSON.parse(JSON.stringify(req.cookies))); // logs {}
console.log(JSON.parse(JSON.stringify(req.signedCookies))); // logs {}
// do stuff with cookie
});
Headers
{
...
cookie: ''
}
Cookie in Headers is an empty string
Network tab:
Got this working. Eventually the solution was that the Load balancer automatically forwards these headers to the backend silently. For my /logout api, instead of trying to grab the cookies from the headers, I set them regardless. Something like this:
app.get('/logout', (req, res) => {
res.cookie("AWSELBSessionCookie", "", {
maxAge: -1,
expires: Date.now(),
path: '/'
}
res.setHeader("Cache-Control", "must-revalidate, no-store, max-age=0");
res.setHeader("Pragma", "no-cache");
res.setHeader("Expires", -1);
res.redirect("https://my-login-page.com");
})