API Requests that need Authorization return CORS error - api

Initial Problem
I work on a web application (react) that accesses data via an API. The API runs for development reasons on a docker container on my local machine. Simple GET requests (via axios) got me CORS complications (...has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.).
A bit of researching solved my problem by running a nginx reverse proxy in another container. I basically used this configuration for the nginx server.
New Problem
As I progress in building my application, I come to a point where I need to send the JWT to the API to access and alter some entries. Requests that need sending a JWT again get me CORS error messages.
The API checks the JWT signature (RS256 generated). I just have to forward it to the API server.
ALSO: simple curl requests with the JWT from the console are working.
Configuration
axios
const axiosConfig = {
responseType: "json",
withCredentials: false,
mode: "no-cors",
headers: {
'Access-Control-Allow-Origin': "*",
'Access-Control-Allow-Credentials': true,
'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE,PATCH,OPTIONS',
'Content-Type': 'application/json',
'Authorization': 'Bearer <JWT as string>',
},
};
const apiGetRequest = async (route, callback) => {
try {
const apiUrl = URL + route;
axios.get(apiUrl, {
axiosConfig
})
.then(res => {
callback(res);
})
.catch(function (error) {
console.log(error);
});
} catch (error) {
console.log(error);
}
}
nginx configuration
Docker Image for api
version: "3.9"
services:
db:
image: mariadb:latest
container_name: db
env_file:
- ./mariadb/.env
volumes:
- ./mariadb/create-schema-docker.sh /docker-entrypoint-initdb.d
- db-data:/var/lib/mysql
ports:
- 3306:3306
rest:
image: mds4ul/station-registry:latest
container_name: api
environment:
- DB_HOST=db
- CONTEXT_PATH=api
env_file:
- ./rest/.env
depends_on:
- db
ports:
- 80:8080
volumes:
db-data:
Questions
Why do I get CORS errors for requests where a jwt is needed and not for requests that do not require one?
Which part do I have to change to make this work?

So answer another question to an embarrassing easy problem of mine.
I switched to an express.js proxy server with the following configuration:
const express = require('express')
const app = express()
const axios = require('axios')
const cors = require('cors')
var bodyParser = require('body-parser')
app.use(cors({
origin: '*'
}))
app.use(bodyParser.json())
require('dotenv').config()
const headers = {
"X-Authorization": <token>,
}
app.get(':endpoint([\\/\\w\\.-]*)', function (req, res) {
const endpoint = (process.env.API_BASE_URL).replace(/\/$/, "") + req.params.endpoint;
axios.get(endpoint, { headers }).then(response => {
res.json(response.data)
}).catch(error => {
res.json(error)
})
})
app.listen(3001)
I assume I just could not figure out my nginx configuration for this use case. So with express.js I can access now resources which need authorization.

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

Invalid csrf token with NestJS

I would like to implement Csrf protection with NestJS and Quasar.
But I think I misunderstand something...
btw I'm not doing SSR, so I don't send the form from the back to the view.
Here is the NestJs back-end code:
async function bootstrap() {
const PORT = process.env.PORT;
const app = await NestFactory.create(AppModule, {
cors: true,
bodyParser: false,
});
console.log(`your App is listening on port ${PORT}`);
// Added Cookie-parser to user csurf packages
// Prevent CSRF attack
app.use(cookieParser());
app.use(csurf({ cookie: true }));
await app.listen(PORT);
}
bootstrap();
So I'm just using CookieParser and csurf package.
On my login page I call a "csrf endpoint" just to send a cookie to the view, to send it back with the post call (login).
I still get the "invalid csrf token" AND a CORS error and don't know why....(see screen below), any suggestions to make it works ?
When I try to login, error in the browser:
And error in the back-end:
Same error if I try a request with insomnia.
I thought that the CSRF token is attached to the "web browser" to go back to the back-end with nest request, so why I'm still getting this error ?
Insomnia send the cookie automatically with the right request so the token should go back to the back-end.
Any idea ?
Regards
EDIT:
After many times reading docs, It seems that CSRF protection is for SSR only ? No need to add csrf security with SPA ? Could anyone can confirm ?
EDIT: Here's another work:
The purpose here is to send a request before login to get a csrf token that I can put into a cookie to resend when I login with a POST method.
Here is my endpoint:
import { Controller, Get, Req, Res, HttpCode, Query } from "#nestjs/common";
#Controller("csrf")
export class SecurityController {
#Get("")
#HttpCode(200)
async getNewToken(#Req() req, #Res() res) {
const csrfToken = req.csrfToken();
res.send({ csrfToken });
}
}
Here is what I've done into my main.ts file (I'll explain below):
async function bootstrap() {
const PORT = process.env.PORT;
const app = await NestFactory.create(AppModule, {
cors: {
origin: "*",
methods: ["GET,HEAD,OPTIONS,POST,PUT"],
allowedHeaders: [
"Content-Type",
"X-CSRF-TOKEN",
"access-control-allow-methods",
"Access-Control-Allow-Origin",
"access-control-allow-credentials",
"access-control-allow-headers",
],
credentials: true,
},
bodyParser: false,
});
app.use(cookieParser());
app.use(csurf({ cookie: true }));
console.log(`your App is listening on port ${PORT}`);
await app.listen(PORT);
}
bootstrap();
And here my axiosInstance Interceptors of the request in my VueJS frontend:
axiosInstance.interceptors.request.use(
(req) => {
const token = Cookies.get('my_cookie')
if (token) {
req.headers.common['Authorization'] = 'Bearer ' + token.access_token
}
req.headers['Access-Control-Allow-Origin'] = '*'
req.headers['Access-Control-Allow-Credentials'] = 'true'
req.headers['Access-Control-Allow-Methods'] = 'GET,HEAD,OPTIONS,POST,PUT'
req.headers['Access-Control-Allow-Headers'] =
'access-control-allow-credentials,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,content-type,x-csrf-token'
const csrfToken = Cookies.get('X-CSRF-TOKEN')
if (csrfToken) {
req.headers['X-CSRF-TOKEN'] = csrfToken
console.log(req)
}
return req
},
(err) => {
console.log(err)
},
Here the same for repsonse:
axiosInstance.interceptors.response.use(
(response) => {
if (response?.data?.csrfToken) {
const {
data: { csrfToken },
} = response
Cookies.set('X-CSRF-TOKEN', csrfToken)
}
return response
},
And inside my login I make a call on the mounted function of my login component:
async mounted() {
const result = await securityService.getCsrf()
},
So now to explain:
As I said I'm not building a SSR project, that's why I want to send the token into a classic axios reponse and store it in a Cookie (this part is for test I heard that storing a csrf token into a classic cookie is not the right way.)
And for each next request I get the csrf token and "attach" it to the request into the headers, making my headers "custom".
Here is a problem I don't know how to make custom headers works with nestJS and CORS, that's why I try many thing with CORS options in NestJS and writte some custome header before the request go to the back-end but without success, I've got the same error message:
I'm a bit confuse about this problem and CORS/CSRF is a big deal for spa, my questions still the same, with CORS and SameSite cookie attributes, and my api is in a subdomain of my front-end, is it really necessary to make a anti-csrf pattern ?
Btw how can I make my custom headers working and why CORS say to me there is no "Access-Control-Allow-Origin" header but there is:
try to generate csrf token and pass to front on each petition
// main.ts - from NestJs - Backend
// after app.use(csurf({ cookie: true }))
app.use((req: any, res: any, next: any) => {
const token = req.csrfToken()
res.cookie("XSRF-TOKEN", token)
res.locals.csrfToken = token
next()
})
from: https://github.com/nestjs/nest/issues/6552#issuecomment-1175270849

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.

Why can't I connect to localhost server from localhost client (cors error)?

I have a local (Angular) client running on port 4200 (http://localhost:4200) and a local (express) server on port 5000 (http://localhost:5000). Whenever I try to connect to my server, I get this message.
Access to XMLHttpRequest at 'http://localhost:5000/socket.io/?EIO=4&transport=polling&t=NU7H' from origin
'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Here is the code to start my local server
#injectable()
export default class App {
app: express.Application;
constructor() {
this.app = express();
this.config();
this.bindRoutes();
}
// Middlewares config
private config(): void {
this.app.use(cors());
this.app.use(express.json());
this.app.use(express.urlencoded({ extended: true }));
}
bindRoutes(): void {
this.app.use('/', router);
}
}
Here is the code where I set up my socket
private _ioServer: SocketIO.Server;
initSocket(server: http.Server) {
this._ioServer = new SocketIO.Server(server);
this.connectChat(); // Chat namespace
this.connectStream(); // Game board streaming namespace
}
I tried with Postman, everything is working.
Thank you!
Any malicious site can take advantage of your cookies stored in the system called Cross-site request forgery
Any browser tries to prevent you from these attacks so they disable CORS.
Shorthand Fix [Not recommended] : There are many plugins out there you can use for your local testing that disables these checks on browser.
Proper Fix: Use an Express middleware to apply Access-Control-Allow-Origin: * in your header when response is returned back from the server.
Gist is that when browser sends the request to your server it will append Origin: http://localhost:3000 to the headers. Reacting to this request from browser, server should return a Access-Control-Allow-Origin header to specify which origins can access the server's resources.
You can be strict here to return Access-Control-Allow-Origin: http://localhost:4200 or open your gates by sending Access-Control-Allow-Origin: *.
Here is the quick code to have an express middleware:
const express = require('express');
const request = require('request');
const app = express();
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
next();
});
app.get('/jokes/random', (req, res) => {
request(
{ url: 'https://joke-api-strict-cors.appspot.com/jokes/random' },
(error, response, body) => {
if (error || response.statusCode !== 200) {
return res.status(500).json({ type: 'error', message: err.message });
}
res.json(JSON.parse(body));
}
)
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`listening on ${PORT}`));
Source: https://medium.com/#dtkatz/3-ways-to-fix-the-cors-error-and-how-access-control-allow-origin-works-d97d55946d9
P.S, this is a very good read for your understanding of CORS.
In the index.js file of your middleware add:
app.use(cors())

Hapi send request to current local server

I have a graphql running on my server. And I have an upload route like this:
server.route({
config: {
cors: {
origin: ['*'],
credentials: true
},
payload: {
output: 'stream',
parse: true,
maxBytes: 50869457,
allow: 'multipart/form-data'
},
},
method: ['POST', 'PUT'],
path: '/uploadAvatar',
handler: (request, reply) => {
const data = request.payload;
data.identity = options.safeGuard.authenticate(request);
// REQUEST TO THE SAME SERVER THIS IS RUNNING ON
}
});
I want to send a request to the same server as I am in if that makes sense.. How to do that?
btw I want to call localhost:3004/graphql if it's running on localhost:3004 but on production it's running on port 80.
You can use hapi's built in server.inject method for handling internal routing, the docs for inject are here