cookies are not accessible from getServerSideProps in NextJs - node.js

i have an issue when trying to send the cookies from browser to nextJs server
first let me describe the issue :
my backend API is built with Nodejs and express, the frontend is
handled by Nextjs , when the user try to login the token is stored as
an httpOnly cookie ,to get the loggedin user i must send the cookie
from my frontend to my backend so i do that through nextJs
getServerSideProps function with the help of
Context.req.headers.cookie in order to sent this token to my nodeJs
backend.
this is the login controller (backend):
exports.login = catchAsync(async (req,res,next)=>{
// SOME CODE
const token = jwt.sign({id: user.id}, process.env.JWT_SECRET);
const cookieOption = {
expires: new Date(Date.now() + process.env.JWT_COOKIE_EXPIRES_IN*24*60*60*1000),
httpOnly: true,
secure : true,
sameSite: 'none',
// domain: '.herokuapp.com'
};
res.cookie('jwt', token, cookieOption);
res.status(200).json({
status: 'success',
data: {
user
}
})
});
and this is my getServerSideProps function in which i try to send the token from nextJs to nodeJs server:
export async function getServerSideProps(context){
console.log(context.req.headers.cookie) // EMPTY OBJECT!
console.log(context.req.cookies) //EMPTY OBJECT TOO
let user
try{
const res = await fetch(`${url}/users/isLoggedin`, {
method: 'GET',
credentials:'include',
headers: {
'Access-Control-Allow-Credentials': true,
Cookie: req.headers.cookie
},
})
const data = await res.json()
if(!res.ok){
throw data
}
user = data
}catch(err){
return console.log(err)
}
return {
props: {
user,
}
}
}
i can send the token from browser directly to nodeJs (client side) but i can't send the token from the browser to nextJs server i don't know why, i have tried many solutions but no ones workes for me .
note: both backend and frontend are deployed to heroku

Related

Unable to retrieve a refresh cookie in expressjs (react and nodejs app)

I am unable to retrieve a cookie that I sent earlier.
As part of login, I sent back a refresh token as an httpOnly cookie.
const payload = {name, email};
console.log("payload: ", payload);
const accessToken = jsonwebtoken.sign(payload, process.env.ACCESS_TOKEN_KEY, { expiresIn: '15m' });
const refreshToken = jsonwebtoken.sign(payload, process.env.REFRESH_TOKEN_KEY, { expiresIn: '1d' });
console.log("Access Token:", accessToken); // access token is generated
console.log("Refresh Token:", refreshToken); // refresh token is generated
res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: false, sameSite: 'Lax', maxAge: 24*60*60*1000 }); // call succeeded. what is the name of cookie?
res.json({ accessToken });
Later on a refresh endpoint I look for a cookie and can't find it:
export const handleRefreshToken = async (req, res) => {
console.log("Request Cookies", req.cookies);
const cookies = req.cookies;
if (!cookies?.refreshToken) return res.sendStatus(401);
I see the following cookies:
_ga: 'xxxxxxxxxxxxxxxxx',
_gid: 'xxxxxxxxxxxxxxxx',
_gat_gtag_UA_xxxxxx: 'x',
_ga_QPY49S2WC4: 'xxxxxxxxxxxxxxxxxxx'
This is on my dev environment with nodejs running on localhost:5000.
Update: Using devtools (Network) I see the cookie in the response on the client side. The name of the cookie is 'refreshToken'. However, the cookie doesn't show up on the browser when I look at the cookies on the browser. Perhaps, the cookie isn't being retained on the browser. Any suggestions on why that could be?
Update2: The link provided by #Konrad Linkowski worked. When the axios request is made from the client, I needed the option "{ withCredentials: true }".
The error was on the client end. The express code functioned correctly. This link explains it: Does Axios support Set-Cookie? Is it possible to authenticate through Axios HTTP request?
My original call on the client side (using axios) was:
const res = await axios.post('/login', { ident: email, password });
Instead it should have been:
const res = await axios.post('/login', { ident: email, password }, { withCredentials: true });

Unable to send auth token to server axios

I am trying to set up a simple login system for a small project. I have managed to connect the website to an login api hosted locally via mysql database.
I'm using express/nodejs on backend and Vue for front end. Also using axios to send http requests.
The error i get is POST http://localhost:3001/api/get-user 422 (Unprocessable Entity)
"{"message":"Please provide the token"}"
Client side part of code.
finally{
const auth = await axios.post(`/api/get-user`, {
headers: {
Authorization: `Bearer ${this.Token}`
}
})
}
Server side part.
router.post('/get-user', signupValidation, (req, res, next) => {
if(
!req.headers.authorization ||
!req.headers.authorization.startsWith('Bearer') ||
!req.headers.authorization.split(' ')[1]
){
return res.status(422).json({
message: "Please provide the token",
});
}
const theToken = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(theToken, 'the-super-strong-secrect');
db.query('SELECT * FROM users where id=?', decoded.id, function (error, results, fields) {
if (error) throw error;
return res.send({ error: false, data: results[0], message: 'Fetch Successfully.' });
});
});
I have put a base URL.
The login is working 100% and I am able to extract the token from the response data.
I used Postman to send the request to the server to get the user and it works perfectly. I believe the issue is in the code of the client side or maybe the client side is sending the token incorrectly where the server side cant read it... I'm not sure please help.
The second parameter in axios post is the body
finally{
const auth = await axios.post(`/api/get-user`,{}, {
headers: {
Authorization: `Bearer ${this.Token}`
}
})
}

Cookies not saving when being set from json response

I am trying to save the access_token and refresh_token of a user after oAuth.
Flow:
User authenticates and I retrieve their accessToken, refreshToken, and user data from the social site
I send data to a remix resource route
In the action for the remix-resource route, I save the data and then try to set the access token and refresh token using 'Set-Cookie' but it doesn't work.
Including the relevant part of my /api/setUser resource route where I try to set the cookie
export let action: ActionFunction = async ({ request }) => {
const session = await sessionStorage.getSession(
request.headers.get('Cookie')
);
const jsonData = await request.json();
session.set('access_token', jsonData['accessToken']);
session.set('refresh_token', jsonData['refreshToken']);
return json<LoaderData>(
{ status: 'ok' },
{
headers: {
'Set-Cookie': await sessionStorage.commitSession(session),
},
}
);
};
Make sure that in your createCookieSessionStorage that you have the cookie path set to / otherwise, the cookie is only visible on the route you set it.

Heroku: cannot share httpOnly cookies between subdomains

I build the simple application that shows GitHub repositories. Here is the link for the app: https://wemake-services-test-client.herokuapp.com (firstly, you need to authenticate to GitHub). It seems to me that fetch doesn't send httpOnly cookies. Click "Sign In" and open the console:
Console Snapshot
Backend handler for route '/login' sets the httpOnly cookie:
import { Request, Response } from 'express';
import { createOAuthAppAuth } from '#octokit/auth-oauth-app';
import cookieOptions from '../cookieOptions';
export default async function login(req: Request, res: Response) {
const code = req.query.code as string;
const appAuth = createOAuthAppAuth({
clientType: 'oauth-app',
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
});
try {
const userAuth = await appAuth({
type: 'oauth-user',
code,
});
const { token } = userAuth;
res.cookie('ws_token', token, cookieOptions);
res.json({ isAuthenticated: true });
...
Then I make GET to '/user' route which has the authMiddleware that checks if there is 'ws_token' in req.cookies and send status 401 (Unauthorized) if not.
authMiddleware.ts:
export default function authMiddleware(
req: Request,
res: Response,
next: NextFunction
) {
if (!req.cookies.ws_token) {
res.sendStatus(401);
return;
}
next();
}
So I got 401 error on '/user' because fetch doesn't send the httpOnly cookie ws_token from client side.
...
async fetchUser(): Promise<GithubUser> {
const response = await fetch(`${API_URL}/user`, {
headers: {
Accept: 'application/json',
},
credentials: 'include',
});
const user: GithubUser = await response.json();
return user;
}
...
It works on localhost (without flag secure and domain set to localhost), but not on Heroku. But why? How to fix it?
Source code:
frontend – https://github.com/standbyoneself/ws-test-client (calls to API using fetch are in src/services/GithubService.ts)
backend – https://github.com/standbyoneself/ws-test-server
Solved by disabling "Prevent cross-site tracking" in Safari Security Settings.

Having trouble with my node-Api validating my react client-side jwt-token

I've just started using Auth0 and its really cool, but im running into some issues.
My main issue is that I have a react client-side app that is saving the jwt token on user login - which is great. However when I try to fetch data from my separate Node API - the route that is supposed to validate the token is giving me errors.
If I have my node api using this first type of authentication, I get an error: UnauthorizedError: secret or public key must be provided
const checkJwt = jwt({
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: "https://smbtodos.auth0.com/.well-known/jwks.json"
}),
audience: 'https://localhost:3001/api/jokes/celebrity',
issuer: "https://smbtodos.auth0.com/",
algorithms: ['RS256']
});
BUT, if I use this second form of validation, it seems to work. My concern is that Im not 100% sure its as secure. If there is no token - this validation give me this error when the token is not valid: UnauthorizedError: jwt malformed
const authCheck = jwt({
secret: 'my-secret',
// If your Auth0 client was created before Dec 6, 2016,
// uncomment the line below and remove the line above
// secret: new Buffer('AUTH0_SECRET', 'base64'),
audience: 'my-domain'
});
Here is my lock file on react:
import { setSecret } from './auth'
import uuid from 'uuid'
const getLock = (options) => {
const config = require('../config.json')
const Auth0Lock = require('auth0-lock').default
return new Auth0Lock(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_DOMAIN, options)
}
const getBaseUrl = () => `${window.location.protocol}//${window.location.host}`
const getOptions = (container) => {
return {
allowedConnections: ['Username-Password-Authentication'],
container,
closable: false,
auth: {
responseType: 'token id_token',
domain: 'smbtodos.auth0.com',
redirectUrl: `${getBaseUrl()}/auth/signed-in`,
params: {
scope: 'openid profile email'
}
}
}
}
export const show = (container) => getLock(getOptions(container)).show()
export const logout = () => getLock().logout({ returnTo: getBaseUrl() })
And here is my api call in react:
static getJokes(access){
console.log(access.token)
return new Promise((resolve, reject) => {
fetch('http://localhost:3001/api/jokes/celebrity', {
method: 'GET',
headers: {
Authorization: `Bearer ${access.token}`,
}
})
.then( r => {
const result = r.json()
return result
} )
.then( res => {
resolve({jokes: res})
console.log('api resp 200');
})
.catch(e => {
reject(e)
// console.log(e)
})
});
}
}
So do I need to make the first option work for better security, if so how? Is the second option of api validation just as good? I feel like I've looked at over 100 tutorials over the last 2 days and they are either out of day, or just are not easy to follow. Im using the most current version of Auth0.
Looking for any help - thank you.

Resources