How To Get HTTP Authorization header With Express and Apollo-Server - node.js

I can not access the "Authorization" header in each HTTP request on my Apollo-Server, implemented with express.
Here is my setup of express, Apollo-Server, CORS, etc.
const corsConfig = {
credentials: true,
allowedHeaders: ['Authorization'],
exposedHeaders: ['Authorization']
};
const app = express()
const server = new ApolloServer({
schema,
context: ({ req }) => {
return {
req
};
}
});
server.applyMiddleware({
app,
path,
cors: corsConfig
});
http.createServer(app).listen(port, () => logger.info(`Service started on port ${port}`));
And inside my resolvers, I bring in the context, particularly the req object (this is an example graphQL endpoint resolver):
const exampleQuery = async (parent, input , { req }) => {
console.log(req.headers);
/*
The output of this log:
{
'content-type': 'application/json',
accept: '*/*',
'content-length': '59',
'user-agent': 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)',
'accept-encoding': 'gzip,deflate',
connection: 'close',
host: 'localhost:3301',
'Access-Control-Allow-Headers': 'Authorization',
'Access-Control-Expose-Headers': 'Authorization'
}
*/
}
I have sent requests to this endpoint, with an "Authorization" header, containing a token as the value. However, the Authorization header is not in the req.headers object (in fact, it's not in the entire req object either). I am certain that my Postman/Insomnia HTTP requests to this endpoint are sending out the Authorization header, however it seems to be not getting through my Apollo-Server.
Anyone have any insight as to why the Authorization header is not going through?
SOLUTION:
The problem was actually that I am using an Apollo federated microservices architecture, which requires additional configuration on the gateway to pass the Authorization header onto the individual microservices, where the resolvers are. You have to add the buildService function inside the ApolloGateway constructor, where you specify that a RemoteGraphQLDataSource willSendRequest of context.req.headers.authentication to the underlying microservices

It works as expected, E.g.
server.ts:
import { ApolloServer, gql, makeExecutableSchema } from 'apollo-server-express';
import express from 'express';
import http from 'http';
const corsConfig = {
credentials: true,
allowedHeaders: ['Authorization'],
exposedHeaders: ['Authorization'],
};
const typeDefs = gql`
type Query {
hello: String
}
`;
const resolvers = {
Query: {
hello: (_, __, { req }) => {
console.log(req.headers);
return 'world';
},
},
};
const schema = makeExecutableSchema({ typeDefs, resolvers });
const app = express();
const path = '/graphql';
const port = 3000;
const server = new ApolloServer({
schema,
context: ({ req }) => {
return {
req,
};
},
});
server.applyMiddleware({ app, path, cors: corsConfig });
http.createServer(app).listen(port, () => console.info(`Service started on port ${port}`));
Send a GraphQL query HTTP request via curl:
curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer abc123" --data '{ "query": "{ hello }" }' http://localhost:3000/graphql
{"data":{"hello":"world"}}
Server-side logs:
Service started on port 3000
{ host: 'localhost:3000',
'user-agent': 'curl/7.54.0',
accept: '*/*',
'content-type': 'application/json',
authorization: 'Bearer abc123',
'content-length': '24' }

Related

Why Axios request to a node(Express) back-end api still preflight OPTIONS request even if http-proxy-middleware is configured?

I'm trying to figure out why when I make a call to my "API" (nodejs express) from my React client the OPTIONS, preflight request is executed first.
Server api run on http://localhost:8000
Client run on http://localhost:3000
In my React client I configured http-proxy-middleware as follows:
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:8000',
changeOrigin: true,
})
);
};
Also the Axios instance is created with following options
const options = {
baseURL: BASE_URL,
timeout: 300000,
withCredentials: false,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
}
};
axios.create(config);
The Node.js(Express) BackEnd application is configured as follow:
app.use(cors(
{
"origin": "*", // just for test...
"methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
"allowedHeaders": "*" // just for test
}
));
When I make a Post for instance for server-side token validation a preflight OPTIONS request is made. How can I avoid this ? Am I doing something wrong?
Thank you all.

Forwarding FormData() request from NodeJS to another service

I am trying to forward my request from my NodeJS Proxy server to another server. The request I am trying to forward contains FormData()
I created FormData as per MDN docs
const payload = new FormData();
payload.append('addresses', file); // <---- UPLOADED FILE
payload.append('reason', 'reason');
payload.append('type', 'type');
This is how I am essentially sending the request to my NodeJS server
fetch("localhost:3000/v1/addresses", {
method: 'PUT',
body: payload
});
NodeJS Server at localhost:3000
const multer = require('multer');
const upload = multer();
app.put('/v1/addresses', upload.single('addresses'), (req, res) => {
let options = {
host: 'localhost',
method: 'PUT',
port: 8000,
path: req.originalUrl,
headers: req.headers,
formData: {
reason: req.body.reason,
type: req.body.type,
}
};
console.log("reason", req.body.reason) // "reason"
console.log("type", req.body.type) // "type"
console.log("addresses", req.file) // FILE OBJECT
const request = http.request(options, response => {
res.writeHead(response.statusCode, response.headers);
response.pipe(res);
});
request.end();
})
The code above, I'm not sure how to send over the actual file to the other service. Also, I am NOT seeing the reason and and type that I've passed over to the service.
What's also strange is that I see this in the incoming request in my NON- PROXY server
PUT /v1/addresses HTTP/1.1
Host: localhost:3000
Connection: keep-alive
Content-Length: 932
Sec-Ch-Ua: "Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryt2p0AWOqJCnz95hg
Accept: */*
Origin: http://localhost:3000
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:3000/blocklist
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
[object Object]
So after lots of searching and experimenting, this post actually provided me with the answer
Here is the code from the post.
const express = require("express");
const app = express();
const bodyParser = require('body-parser');
var multer = require('multer')();
const FormData = require('form-data');
const axios = require('axios');
const fs = require('fs');
app.use(bodyParser.json());
app.post('/fileUpload' , multer.single('fileFieldName'), (req , res) => {
const fileRecievedFromClient = req.file; //File Object sent in 'fileFieldName' field in multipart/form-data
console.log(req.file)
let form = new FormData();
form.append('fileFieldName', fileRecievedFromClient.buffer, fileRecievedFromClient.originalname);
axios.post('http://server2url/fileUploadToServer2', form, {
headers: {
'Content-Type': `multipart/form-data; boundary=${form._boundary}`
}
}).then((responseFromServer2) => {
res.send("SUCCESS")
}).catch((err) => {
res.send("ERROR")
})
})
const server = app.listen(3000, function () {
console.log('Server listening on port 3000');
});

CORS error with socket-io connections on Chrome v88+ and Nestjs server

I have an application connecting to a Nestjs server to establish a WS connection (server is on a different URL, so it is a CORS request).
The WebsocketGateway is defined as such.
#WebSocketGateway(port, {
handlePreflightRequest: (req, res) => {
const headers = {
'Access-Control-Allow-Headers': 'Authorization',
'Access-Control-Allow-Origin': 'the page origin',
'Access-Control-Allow-Credentials': true,
};
res.writeHead(200, headers);
res.end();
}
})
Works like a charm on Chrome v87 and down and on Firefox. Since upgrading my browser to Chrome 88, the front-end socket-io connection goes on a connect-reconnect loop, as:
The preflight request passes and gets a 200 response, with the headers set above;
The actual connection fails with CORS error as the only message in the browser console
Just incase someone else needs this, in your decorator there is a cors property
#WebSocketGateway({ cors: true })
This is how i fixed
import { IoAdapter } from '#nestjs/platform-socket.io';
import { ServerOptions } from 'socket.io';
export class SocketAdapter extends IoAdapter {
createIOServer(
port: number,
options?: ServerOptions & {
namespace?: string;
server?: any;
},
) {
const server = super.createIOServer(port, { ...options, cors: true });
return server;
}
}
main.ts
const app = await NestFactory.create(AppModule, { cors: true });
app.useWebSocketAdapter(new SocketAdapter(app));

Cannot view authorization header

I'm using node.js default HTTP module and have an HTTP webserver.
I use request.headers to get all headers, but when I try to do request.headers.authorization it returns undefined, but there IS authorization as you can see here.
I tried to do JSON.parse(request.headers).authorization, still undefined, and crashes the process. How can I get the authorization header content?
Maybe you could use the method request.getHeader(name)
I looked in https://nodejs.org/api/http.html#http_request_getheader_name
Edit 1:
index.js
const http = require('http');
const server = http.createServer((request, response) => {
console.log(request.headers);
console.log(request.headers.authorization);
console.log('----');
const headers = {
'Content-Type': 'text/plain',
};
let statusCode = 404;
response.writeHead(200, headers);
response.end('Hi');
});
server.listen(8080, () => {
console.log(`Server listening on port :8080 🚀`);
});
Executing in terminal one:
node index.js
Terminal two:
curl localhost:8080 -H 'authorization: hello'
The output in terminal one is:
{ host: 'localhost:8080',
'user-agent': 'curl/7.68.0',
accept: '*/*',
authorization: 'hello' }
hello
----

How to fix "Response to preflight request doesn't pass access control check: It does not have HTTP ok status" error in react app with nodejs api

I have been trying to do an api call (nodejs with express running on localhost) from a react app running in the browser over a local dev server (web-pack dev server). Everything was working well until I tried to call the api. They are both running on separate ports.
I have tried adding the cors headers (Recommended by MDN) to both the post call (from the app in browser) and to the response from the Nodejs API but neither of these solved the issue.
Code for the api call (in browser):
const headers = {
'Content-Type': 'application/json',
'access-token': '',
'Access-Control-Allow-Origin': '*',
}
export default async () => {
try {
const body = JSON.stringify({
test: true,
})
const response = await fetch('http://localhost:1337/internal/provider/check_email_exist', {
method: 'POST',
headers,
body,
})
console.log(response)
} catch (e) {
return e
}
}
API Middleware (in nodejs):
// Verify All Requests
app.use(VerifyToken)
// Compress
app.use(compression())
// Helmet middlware
app.use(helmet())
// Body Parser
app.use(bodyParser.urlencoded({
extended: false,
}))
app.use(bodyParser.json())
The expected result is to just give a 200 status code and respond with the data.
The actual output is:
OPTIONS http://localhost:1337/internal/provider/check_email_exist 404 (Not Found)
Access to fetch at 'http://localhost:1337/internal/provider/check_email_exist' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
Since you're using webpack-dev-server you can use the proxy option DevServerProxy.
Your configuration will look like this:
// webpack.config.js
devServer: {
proxy: {
'/internal': 'http://localhost:1337'
}
}
Since I can't see your express routes on your question I'm speculating about the proxy route if your API lives on /internal endpoint then you should modify your React code like this:
const response = await fetch('/internal/provider/check_email_exist', {
method: 'POST',
headers,
body,
})
As you can see I ommited the https://localhost:1337 because the proxy option from webpack-dev-server will handle this and it will redirect to http://localhost:1337. Hope this will help you. Cheers, sigfried.
EDIT
As the comment on your question pointed out you should set the headers on your express server, not the client, for this task you can use the cors-middleware package.
Maybe this can help if you face with preflight errors.
My full config:
const cors = require('cors');
const express = require('express');
const { createProxyMiddleware: proxy } = require('http-proxy-middleware');
...
const logLevel = 'info';
const ip = require('ip').address();
const proxyOptions = {
xfwd: true,
target,
changeOrigin: true,
logLevel,
cookieDomainRewrite: {
'*': 'localhost',
},
headers: {
'X-Forwarded-For': ip,
'X-Node': 'true',
},
};
const backNginxApp = express();
backNginxApp.use(
cors({
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
origin: 'http://localhost:3000',
optionsSuccessStatus: 200,
credentials: true,
})
);
backNginxApp.use('/api', proxy(proxyOptions));
API: const target = 'https://someapi.com'
Local development running at: http://localhost:3000

Resources