Get Express app's response header in Angular 7 client - node.js

I've been trying to access the http response header from a NodeJS app in the Angular 7 client code, but I'm unable to retrieve it. I've tried every solution I found on Stack Overflow. I still keep getting an empty header back in the browser. But it works fine on Postman.
I'm attaching the browser console log below:
headers: HttpHeaders
lazyInit: ƒ ()
lazyUpdate: null
normalizedNames: Map(0)
size: 0
__proto__: Map
[[Entries]]: Array(0)
length: 0
Server-side code:
app.js
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const route = require('./routes/route');
const { mongoose } = require('./db/mongoose');
const app = express();
app.use(bodyParser.json());
app.use(cors({
'allowedHeaders': ['sessionId', 'Content-Type', 'x-auth', 'Authorization'],
'exposedHeaders': ['sessionId', 'x-auth', 'Authorization'],
'origin': '*',
'methods': 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
'preflightContinue': false
}));
// app.options('*', cors());
app.use(route);
app.listen(3000);
controller.js
exports.signupUser = (req, res, next) => {
let user = new UserModel({
email: req.body.email,
password: req.body.password
});
user.save().then((user) => {
return user.generateAuthToken();
}).then((token) => {
res.header({
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept, Authorization',
'Access-Control-Expose-Headers': 'Authorization',
'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE,PATCH,OPTIONS',
'Access-Control-Allow-Credentials': 'true',
'x-auth': token
}).send(user);
}).catch((err) => {
res.status(400).send(err);
});
};
angular 7 code:
auth.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class AuthService {
token: string;
constructor(private http: HttpClient) { }
signup(email: string, password: string) {
this.http
.post('http://localhost:3000/signup', { email, password }, {observe: 'response'})
.subscribe((response) => {
console.log(response);
});
}
}

How are you attempting to access your header on the client side?
The response.headers from your example is an instance of HttpHeaders. The headers arent visible as properties on the headers object itself but you can access them like this:
this.http
.post('http://localhost:3000/signup', { email, password }, {observe: 'response'})
.subscribe((response) => {
let contentTypeHeader = response.headers.get("content-type");
console.log(contentTypeHeader);
});

Related

Can't make a successful Authorization request from Axios request to third-party API

I have been dealing with this issue where I am attempting to make a get request to a third-party API using Axios in my Node.js server. The endpoint requires a username and password which I am passing along as follows:
export const getStream = async(req, res) => {
let conn = createConnection(config);
let query = `SELECT * FROM cameras WHERE id = ${req.params.id}`
conn.connect();
conn.query(query, async (error, rows, _) => {
const camera = rows[0];
const {ip, user, pass} = camera;
if (error) {
return res.json({ "status": "failure", "error": error });
}
const tok = `${user}:${pass}`;
const userPass = Buffer.from(tok)
const base64data = userPass.toString('base64');
const basic = `Basic ${base64data}`;
const result = await axios({
method: 'get',
url: `<API URL>`,
headers: {
'Authorization': basic,
'Content-Type': 'multipart/x-mixed-replace; boundary=--myboundary'
},
auth: {username: user, password: pass}
})
res.json(result)
});
conn.end();
}
I am then calling this endpoint in my React front-end as such:
const getStream = async () => {
try {
const result = await publicRequest.get(`camera/getStream/${id}`)
console.log(result)
} catch (error) {
console.error(error)
}
}
Each time I make this request, my node server crashes and I get a 401 unauthorized error in my console. It appears that my Authorization header is not getting passed to the server even though everything else gets passed along as so.
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'multipart/x-mixed-replace; boundary=--myboundary',
'User-Agent': 'axios/0.26.1'
},
method: 'get',
url: '<url>',
auth: { username: '<username>', password: '<password>' },
data: undefined
For extra information, this is how my node server is setup
import express, { urlencoded, json } from 'express';
import userRoute from './routes/userRoute.js';
import cameraRoute from './routes/cameraRoute.js';
import cors from 'cors';
const app = express();
app.use(cors());
app.options('*', cors());
app.use(json())
app.use(urlencoded({ extended: true }));
app.use(express.static('public'));
app.use('/api/user', userRoute);
app.use('/api/camera', cameraRoute);
const port = process.env.PORT || 8080;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
I have been working on this issue for several days and each time I try something new, I always get a 401 error, and the server crashes.
Any suggestions would be greatly appreciated.

gatsbyjs and expressjs Error 400 Bad Request while fetch

I have a small backend with ExpressJS that sends an email, I have deployed this backend on a Heroku site, I have tried it with postman and everything is ok, it works, but when I want to use it from my gatsby site, it throws a problem with cors, the gatsby site is running on my localhost.
ExpressJS code:
import dotenv from 'dotenv';
import express from 'express';
import nodemailer from 'nodemailer';
import cors from 'cors';
if (process.env.NODE_ENV !== 'production') {
dotenv.config();
}
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(cors());
const contactAddress = process.env.CONTACT_ADDRESS || '';
const mailer = nodemailer.createTransport({
service: 'Gmail',
auth: {
user: process.env.GMAIL_ADDRESS,
pass: process.env.GMAIL_PASSWORD,
},
});
app.post('/contact', (req, res) => {
if (!req.body.from)
return res.status(400).json({ message: 'From is required' });
if (!req.body.message)
return res.status(400).json({ message: 'Message is required' });
mailer.sendMail(
{
from: req.body.from,
to: [contactAddress],
subject: 'Contact from API',
html: `<h3>${req.body.from}</h3><br>${req.body.message}`,
},
(err, info) => {
if (err) {
console.log(err);
return res.status(500).send(err);
}
res.status(200).json({ success: true });
}
);
});
app.listen(process.env.PORT || 8000);
console.log(`App running on port ${process.env.PORT || 8000}`);
Code on frontend that make the request:
const onSubmit = async (data: IFormInputs) => {
console.log(data);
const formData = new FormData();
Object.keys(data).forEach((el) => {
formData.append(el, data[el]);
});
try {
const res = await fetch(`${BACKEND_URL}contact`, {
method: 'POST',
body: formData,
});
console.log(res);
} catch (e) {
console.log(e);
}
}
I have also tried adding some configuration on the fetch, but it does not work anyways
const res = await fetch(`${BACKEND_URL}contact`, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json',
},
referrerPolicy: 'origin',
body: formData,
});
The error throws the following error:
{
status: 400,
statusText: "Bad Request",
type: "cors",
ok: false,
}
I have searched similar questions on StackOverflow, but any of the solutions have worked for me.
The answer on this post does not work for me, because I don't have the backend and the frontend on localhost, I am consuming the API from my Heroku site.
Thanks in advance!
You are listening on the port 8000. Are you sure it's correct? Let's try on posts 8080.
You can also set headers:
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", '*');
res.header("Access-Control-Allow-Credentials", true);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header("Access-Control-Allow-Headers", 'Origin,X-Requested-With,Content-Type,Accept,content-type,application/json');
next();
});

Cross-Origin Request Blocked even after adding headers in server code

I've a simple API in Express/Node and I also have a simple angular application for posting blogs. The only problem is when I hit the /contribute route using POST method. I'm getting this error on both chrome and firefox:
error: error { target: XMLHttpRequest, isTrusted: true, lengthComputable: false, … }
​
headers: Object { normalizedNames: Map(0), lazyUpdate: null, headers: Map(0) }
​
message: "Http failure response for localhost:3000/api/contribute: 0 Unknown Error"
​
name: "HttpErrorResponse"
​
ok: false
​
status: 0
​
statusText: "Unknown Error"
​
url: "localhost:3000/api/contribute"
​
: {…}
​​
constructor: class HttpErrorResponse { constructor(init) }​​
: {…}
​​​
constructor: class HttpResponseBase { constructor(init, defaultStatus, defaultStatusText) }​​​
: {…
Here's my server side code.
api.js
...
router.post('/contribute', (req, res) => {
console.log('Pushing new article');
let userPost = req.body;
let post = new Post(userPost);
post.save((error, registeredPost) => {
if (error) {
console.log(error);
} else {
res.status(200).send(registeredPost);
}
})
})
...
module.exports = router;
server.js
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const api = require('./routes/api');
const cors = require('cors');
app.use(bodyParser.json());
// app.use(cors({ origin: 'http://localhost:4200' })); <--- TRIED THIS ALSO
app.use(function (req, res, next) {
// Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:4200');
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader('Access-Control-Allow-Credentials', true);
// Pass to next layer of middleware
next();
});
app.use('/api', api);
app.get('/', function(req, res) {
res.send('Server is up and running!');
})
app.listen(3000, function() {
console.log('Server listening port:3000');
});
Yes, server is up and running.
Here is angular code.
auth.service.ts
private _contributeUrl = "https://localhost:3000/api/contribute";
...
pushNewPost(newPost) {
console.log("here is the new post", newPost); // GETTING CORRECT OUTPUT
return this._http.post<any>(this._contributeUrl, newPost);
}
contribute.component.ts
this._auth.pushNewPost(this.makeNewPost)
.subscribe (
res => {
(<HTMLInputElement>document.getElementById("inputTitle")).value="";
this.editorForm.reset();
this.addSingle();
},
err => console.log(err)
);
Now the fun part is that the same code is working perfectly when I make a post request to this route using Postman without any error.
Please correct my mistake. After adding:
pushNewPost(newPost) {
console.log("here is the new post", newPost);
let headers = new HttpHeaders({
'Content-Type': 'application/json',
});
let options = { headers: headers };
return this._http.post<any>(this._contributeUrl, newPost);
}
I'm getting this:
Seems like you are not sending in the headers from angular. Make the following changes:
pushNewPost(newPost) {
// adding the headers
const headers = new HttpHeaders({
'Content-Type': 'application/json',
});
const options = { headers: headers };
return this._http.post<any>(this._contributeUrl, newPost, options);
}

Cookie is not set using express and passport

I spent a long time trying figure it out why it's not working.
I'm implementing a login page using react.
This page send the user and pass to backend (nodejs + express) using axios:
const login = useCallback(e => {
e.preventDefault()
fetch(process.env.REACT_APP_HOST + '/login', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: e.target.elements.username.value,
password: e.target.elements.password.value
})
})
.then(response => {
if (response.ok) {
return response.json()
} else if (response.status === 401) {
throw new Error('Invalid user or pass.')
} else {
throw new Error('An error ocurred')
}
...
})
}, [])
In backend, I have a route that receive those data and check on ldap system. So I generate a token using JWT and save it on token
const express = require('express'),
passport = require('passport'),
bodyParser = require('body-parser'),
jwt = require('jsonwebtoken'),
cors = require('cors')
cookieParser = require('cookie-parser')
LdapStrategy = require('passport-ldapauth');
let app = express();
app.use(cookieParser())
app.use(cors());
....
app.post('/login', function (req, res, next) {
passport.authenticate('ldapauth', { session: false }, function (err, user, info) {
const token = jwt.sign(user, env.authSecret)
res.cookie('cookie_token', token) //it doesn't set the cookie
if (err) {
return next(err)
}
if (!user) {
res.sendStatus(401)
} else {
return res.status(200).send({ firstName: user.givenName});
}
})(req, res, next);
});
The problem is that the token is empty, it's not being set.
Couple of things. In your react fetch post method you need to add
withCredentials: true,
beside the httpheader.
fetch(process.env.REACT_APP_HOST + '/login', {
method: 'POST',
withCredentials: true,
credentials: 'include',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: e.target.elements.username.value,
password: e.target.elements.password.value
})
})
After that in your nodejs part you are using cors but validating all origin. Thats not going to work with credentials. You need to use cors like this to validate specific origin and also turn credentials to true-
app.use(cors({credentials: true, origin: 'http://localhost:4200'}));
after that you can send your cookie by
res.cookie('cookie_token', token, { maxAge: 900000 })
This way the cookie will arrive and once the cookie is arrived in client side you can retrieve the cookie with document.cookie or with any other package like "js-cookie"

How to set cookies express, react.js

I am building a login system using express for node.js and react.js. In my back-end when a user logs in, it creates a cookie. When I go to Network > Login I can see this:
Set-Cookie:
user_id=s%3A1.E%2FWVGXrIgyXaM4crLOoxO%2Fur0tdjeN6ldABcYOgpOPk; Path=/; HttpOnly; Secure
But when I go to Application > Cookies > http://localhost:3000, there is nothing there. I believe that is because I am not allowing credentials to go through correctly when I do a post request from the client side. How do I go about this? Please, let me know if I can improve my question in any way.
//Login back-end
router.post('/login', (req, res, next) => {
if(validUser(req.body)) {
User
.getOneByEmail(req.body.email)
.then(user => {
if(user) {
bcrypt
.compare(req.body.password_digest, user.password_digest)
.then((result) => {
if(result) {
const isSecure = process.env.NODE_ENV != 'development';
res.cookie('user_id', user.id, {
httpOnly: true,
secure: isSecure,
signed: true
})
res.json({
message: 'Logged in'
});
} else {
next(new Error('Invalid Login'))
}
});
} else {
next(new Error('Invalid Login'))
}
});
} else {
next(new Error('Invalid Login'))
}
});
//Allow CORS index.js
app.use(
cors({
origin: "http://localhost:3000",
credentials: true
})
);
//Login client side (React.js)
loginUser(e, loginEmail, password) {
e.preventDefault();
let email = loginEmail;
let password_digest = password;
let body = JSON.stringify({ email, password_digest });
fetch("http://localhost:5656/api/login", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
credentials: "include",
body
})
.then(response => response.json())
.then(user => {
console.log(user);
});
}
You should be secure of set "credentials" in the server and in app.
Try to set on you index.js or app.js server side this:
app.use(function(req, res, next) {
res.header('Content-Type', 'application/json;charset=UTF-8')
res.header('Access-Control-Allow-Credentials', true)
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
)
next()
})
and in you client site add options like this:
let axiosConfig = {
withCredentials: true,
}
export async function loginUser(data) {
try {
const res = await axios.post(
`${URL}:${PORT}/${API}/signin`,
data,
axiosConfig
)
return res
} catch (error) {
console.log(error)
}
}
Edit
To set "credentials" in server we need this line:
res.header('Access-Control-Allow-Credentials', true)
This would let you handle credentials includes in headers.
You also have to tell to axios to set credentials in headers with:
withCredentials: true
Do not forget to adjust cors middleware.
Your node.js express code
const express = require("express");
const cors = require('cors')
const app = express();
app.use(cors(
{
origin: 'http://localhost:3000',
optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}
));
app.use(function(req, res, next) {
res.header('Content-Type', 'application/json;charset=UTF-8')
res.header('Access-Control-Allow-Credentials', true)
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
)
next()
})
app.get("/auth", function(req, res){
res.cookie('token', 'someauthtoken')
res.json({id: 2});
});
app.listen(3030);
Your front-end code
import React, { useEffect } from 'react';
import axios from 'axios';
async function loginUser() {
try {
const res = await axios.get(
'http://localhost:3030/auth',
{
withCredentials: true,
}
)
return res
} catch (error) {
console.log(error)
}
}
function App() {
useEffect(() => {
loginUser();
}, [])
return (
<div>
</div>
);
}
export default App;
It is because you set httpOnly: true.
This will block the visibility to client side, like reading from javaScript document.cookie().
You can solve this by turn it off.
If you can't see your cookie in the browser, I think it is because you're setting hhtpOnly to true in the cookie's options.
cookie.httpOnly
Specifies the boolean value for the HttpOnly Set-Cookie attribute. When truthy, the HttpOnly attribute is set, otherwise it is not. By default, the HttpOnly attribute is set.
Note: be careful when setting this to true, as compliant clients will not allow client-side JavaScript to see the cookie in document.cookie
res.cookie('user_id', user.id, {
httpOnly: false, // try this
secure: isSecure,
signed: true
})
You need to configure cors in your backend server first.
First, install cors using npm i cors then in your express server add this line of code:
app.use(cors({
origin: "YOUR FRONTEND SITE URL HERE",
credentials: true,
}));
Then, in your frontend app where you are sending GET/POST requests to your backend, make sure to add in your request
If you've used fetch:
const res = await fetch('BACKEND SERVER URL', {
credentials: "include",
// other objects
});
If axios is used:
const res = await axios.post('BACKEND SERVER URL',
{ withCredentials: true },
// other objects,
);
This will solve the problem of storing cookies in frontend sent from backend.

Resources