Set a cookie in Nest JS using POST request (ViteJs + REACT JS) - node.js

I am using nest js and want to set the cookies when the user will hit a specific endpoint:
#Get()
setCookiesApi(#Res({ passthrough: true }) response:Response) {
response.setCookie('key', 'value')
}
This code works and the cookies are set in cookies storage from the Application tab in Chrome. If i try setting cookies using post:
#Post()
setCookiesApi(#Res({ passthrough: true }) response:Response) {
response.setCookie('key', 'value')
}
My code on UI:
try {
const response = await axios.post(
`http://localhost:3000/api/v1/hello`,
user,
{
method: 'post',
headers: {
withCredentials: true,
},
data: user,
},
);
if (response.data) {
// sss
}
} catch (err) {
if (err instanceof AxiosError) {
if (err.response) {
toast.error(err.response.data.message);
}
}
}
main.js file
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api/v1');
app.useGlobalPipes(new ValidationPipe());
app.use(cookieParser());
app.enableCors({ origin: 'http://127.0.0.1:5173', credentials: true });
await app.listen(3000);
}
bootstrap();
...then the cookies storage is empty and no cookie is set. Question: Why get request works but post not and how to solve this?

I think the GET request works because it is an HTTP method that allows for retrieving data from a server, and the setCookie() method is used to set a cookie in the response.
The POST request doesn't work because POST is typically used for creating or modifying data on a server, and the setCookie() method is not designed to handle data modifications.

It works with POST method as well. Try something similar like this:
#Post()
setCookiesApi(#Res({ passthrough: true }) response:Response) {
response.setHeader('Set-Cookie', cookie);
return response.send();
}

Related

Node, how to send result back to client from nested HTTP request?

I'm using ReactJS to run my front-end and using Express for my back-end. I want to make a get request to my back-end using the "/paas" path to get a listing of all of my pods that are running inside my namespace in Rancher(Kubernetes).
The back-end then needs to be able to make an https request to my Rancher API endpoint and return the result to the front-end. I can make the successful call to Rancher API and see the data print to the screen on my back-end but I get lost when trying to send this data to the front-end and console log it out inside the browser.
Due to "pre-flight" errors, I can't just make a direct call to the Rancher endpoint inside of my App.js file. More info on this here. So I need to go the custom back-end route. I any case, it seems like this should be pretty straightforward. Any guidance would be appreciated.
App.js:
import React, { useEffect } from "react"
import axios from "axios"
function App() {
useEffect(() => {
const fecthPods = async () => {
try {
const response = await axios.get(`http://localhost:3001/paas`)
console.log(response.data)
} catch (err) {
if (err.response) {
// Not in the 200 response range
console.log(err.response.data)
console.log(err.response.status)
console.log(err.response.headers)
} else {
console.log(`Error: ${err.message}`)
}
}
}
fecthPods()
},[])
return (
<div>
Hello World!
</div>
);
}
export default App;
Back-end server.js:
import express from "express"
import cors from "cors"
import https from "https"
import bodyParser from "body-parser";
const app = express()
app.use(cors())
app.use("/data", (req, res) => {
res.json({ name: "Minion", favFood: "pizza"})
})
app.get("/paas", bodyParser.json(), (req, res) => {
const options = {
hostname: "k8.fqdn.com",
port: 443,
path: "/k8s/clusters/c-wwfc/v1/pods/mynamespace",
method: "GET",
headers: {
Authorization: "Bearer token:12345"
}
}
const request = https.get(options, (res) => {
let responseBody = ""
res.setEncoding("UTF-8")
res.on("data", (chunk) => {
console.log("---chunk", chunk.length);
responseBody += chunk;
});
res.on("end", () => {
let json = JSON.parse(responseBody)
// console.log(responseBody)
console.log("Response finished");
res.json({data: responseBody})
});
});
request.end()
res.json({ status: "complete", data: request.data})
})
app.listen(3001)
console.log("backend up on 3001")
I see a couple of errors on your backend code.
First, you are naming the res variable for the express middleware and also for the response received by the https module. In this way, you lose the possibility to access to the express response object in the on.('end') callback.
Secondly, you are triyng to respond to the client multiple times (inside the on.('end') callback and also directly inside the express middleware with the instruction res.json({ status: "complete", data: request.data}). Also, consider that the code you wrote is repliyng to the client before the call to the k8s cluster is made. And the response will always be a JSON with this data: { "status": "complete", "data": undefined}.
To fix all, try with this code (I will try to comment all edits):
app.get("/paas", bodyParser.json(), (req, res) => {
const options = {
hostname: "k8.fqdn.com",
port: 443,
path: "/k8s/clusters/c-wwfc/v1/pods/mynamespace",
method: "GET",
headers: {
Authorization: "Bearer token:12345"
}
}
const k8sRequest = https.get(options, (k8sResponse ) => { // as you can see I renamed request and res to k8sRequest and k8sResponse, to avoid loosing the scope on req and res express middleware variables
let responseBody = ""
res.setEncoding("UTF-8")
k8sResponse.on("data", (chunk) => { // here use k8sResponse to collect chunks
console.log("---chunk", chunk.length);
responseBody += chunk;
});
k8sResponse.on("end", () => { // here use k8sResponse again
let json = JSON.parse(responseBody)
// console.log(responseBody)
console.log("Response finished");
res.json({ status: "complete", data: responseBody}) // here use the express res variable, to reply to the client.
});
});
k8sRequest.end() // here use the k8sRequest variable to make the https call to the k8s cluster
// here I deleted the res.json instruction
})
The above code should just works. Anyway, I suggest you using axios also with your backend service. You are already using it with React, so you know how to use it. The syntax is minimal and easier and you can use the async/await approach.
Axios solution:
import axios from "axios"
app.get("/paas", bodyParser.json(), async (req, res) => {
try {
const url = 'https://k8.fqdn.com/k8s/clusters/c-wwfc/v1/pods/mynamespace'
const k8sResponse = await axios.get(url, headers: {
Authorization: "Bearer token:12345"
})
res.json({ status: "complete", data: k8sResponse.data })
} catch (e) {
res.json({status: "error", data: e.response.data})
}
})
You should wrap your axios call inside a try/catch block to properly handle errors like you are doing with your React implementation. Error handling should be also implemented if you still want you the native node.js https module

No 'Access-Control-Allow-Origin' header is present on the requested resource when deployed to Heroku

So I have a simple Heroku app that just signs people up to my Mailchimp contacts list. I have it working locally, but when deploying the app I get CORS errors.
I've tried adding withCredentials: true to my axios request and adding cors origin in my Express app, but it still doesn't work.
I have been going through all kinds of articles on cors issues trying to get this to work and understand why this is happening.
I've replaced some of the sensitive information with dummy text, but this is essentially how the request is being made and what the server is doing with the information.
React Code:
const submitForm = async (values) => {
const { email } = values;
try {
const payload = {
email_address: email
};
await axios.post("https://some-url.herokuapp.com", payload, { withCredentials: true });
handleMessage("Thank you for signing up.");
} catch (error) {
console.log(error.message);
handleMessage(error.message);
}
}
Express Code:
const cors = require('cors');
app.use(cors({
origin: `https://www.mycustomdomain.com/`,
credentials: true
}));
app.post('/', jsonParser, (req, res, next) => {
const { email_address } = req.body
if (!email_address) {
return res.status(400).json({
error: `Missing email in request body`
})
}
const data = {
members: [
{
email_address,
status: 'subscribed'
}
]
}
const payload = JSON.stringify(data);
const options = {
url: `https://mailchim-api.com`,
method: 'POST',
headers: {
Authorization: `auth authCode`
},
body: payload
}
request(options, (err, response, body) => {
if (err) {
res.status(500).json(err)
} else {
if (response.statusCode === 200) {
res.status(200).json(body)
}
}
})
})
My initial problem was that I didn't manually enter the env variables used in the Express app into Heroku.
I had to run heroku config:set APIKEY=SOME-PASSWORD in order to get all of my env variables to be used on Heroku.
https://devcenter.heroku.com/articles/config-vars
Because I didn't do this originally, I made the mistake of replacing all of my env variables with the actual strings which brought about the CORS issue. Mailchimp also deactivated my API key since the key was being published online. So it was actually a layered issue, not exactly a CORS issue, to begin with.

Nodejs - Axios not using Cookie for post request

I'm struggling with AXIOS: it seems that my post request is not using my Cookie.
First of all, I'm creating an Axios Instance as following:
const api = axios.create({
baseURL: 'http://mylocalserver:myport/api/',
header: {
'Content-type' : 'application/json',
},
withCredentials: true,
responseType: 'json'
});
The API I'm trying to interact with is requiring a password, thus I'm defining a variable containing my password:
const password = 'mybeautifulpassword';
First, I need to post a request to create a session, and get the cookie:
const createSession = async() => {
const response = await api.post('session', { password: password});
return response.headers['set-cookie'];
}
Now, by using the returned cookie (stored in cookieAuth variable), I can interact with the API.
I know there is an endpoint allowing me to retrieve informations:
const readInfo = async(cookieAuth) => {
return await api.get('endpoint/a', {
headers: {
Cookie: cookieAuth,
}
})
}
This is working properly.
It's another story when I want to launch a post request.
const createInfo = async(cookieAuth, infoName) => {
try {
const data = JSON.stringify({
name: infoName
})
return await api.post('endpoint/a', {
headers: {
Cookie: cookieAuth,
},
data: data,
})
} catch (error) {
console.log(error);
}
};
When I launch the createInfo method, I got a 401 status (Unauthorized). It looks like Axios is not using my cookieAuth for the post request...
If I'm using Postman to make the same request, it works...
What am I doing wrong in this code? Thanks a lot for your help
I finally found my mistake.
As written in the Axios Doc ( https://axios-http.com/docs/instance )
The specified config will be merged with the instance config.
after creating the instance, I must follow the following structure to perform a post requests:
axios#post(url[, data[, config]])
My requests is working now :
await api.post('endpoint/a', {data: data}, {
headers: {
'Cookie': cookiesAuth
}
});

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.

AWS-Lambda 302 Not Redirecting after getting response from Axios (Frontend)

I'm trying to setup a Google-OAuth flow using serverless and AWS-Lambdas. To start, I have a button that kicks off the process by hitting a lambda endpoint. However, the page never actually redirects to the authentication page. Instead I get an error on the FE:
Request failed with status code 302
Frontend logic:
const redirectToGoogleOAuth = async (user) => {
try {
const endpoint = process.env.GOOGLE_PATH_ENDPOINT;
const response = await axios.get(endpoint, {
responseType: 'text',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${user}`,
},
});
// Expect redirect at this point
return response.data.data;
} catch (err) {
throw new Error(err.message);
}
};
Lambda Endpoint:
module.exports = async (event, context) => {
const responseType = 'code'
const googleAuthorizeURL = 'https://accounts.google.com/o/oauth2/v2/auth'
const scope = 'openid email https://www.googleapis.com/auth/contacts.readonly'
const accessType = 'offline'
try {
const params = [
`response_type=${responseType}`,
`client_id=${googleClientId}`,
`redirect_uri=${baseURL}`,
`scope=${scope}`,
`state="state"`,
`access_type=${accessType}`
]
const googleOAuthEndPath = `${googleAuthorizeURL}?${params.join('&')}`
const response = {
statusCode: 302,
body: '',
headers: {
location: googleOAuthEndPath
}
}
return response
} catch (err) {
return response(400, err.message)
}
}
In the lambda-response, I've added a header for location with the google-path. However, the frontend does not seem to consume the response correctly. The frontend interprets the 302 as in error instead of redirecting to the specific page. Any ideas on how I may resolve this so it actually redirects?
Axios uses XHR, which always follows redirects by itself and therefore Axios can't do anything about it (unless you rely on hacks, discussed in the same link).
You might have to use something other than Axios for this part, such as the Fetch API, which supports manual redirects.
GitHub user parties suggested the fetch() equivalent in the same Axios issue linked above:
fetch("/api/user", {
redirect: "manual"
}).then((res) => {
if (res.type === "opaqueredirect") {
window.location.href = res.url;
} else {
return res;
}
}).catch(handleFetchError);

Resources