I have a nodejs express application hosted on a server. Basically, I have two endpoints:
POST /session - this is where I send back the cookie
GET /resource - this is where I check if the cookie is sent back, if not I send back 401 not found, like so
On the frontend, which is on a different domain (let's say a newly generated angular-cli application which is running on htpp://localhost:4200), I try to call the /session API, which returns the cookie header, but a consecutive /resource API call will not send the cookie back to the server. What am I doing wrong?
Serve code is as follows:
// server.js
const express = require("express");
const app = express();
const cookieParser = require("cookie-parser");
const cors = require("cors");
app.use(cors());
app.use((req, res, next) => {
console.log(req.method, req.url, JSON.stringify(req.headers, null, 2));
next();
});
app.use(cookieParser());
app.get("/", (req, res) => {
res.send({ status: "running" });
});
app.post("/session", (req, res) => {
const cookie = `AuthSession=token; Path=/;`;
res.setHeader("Set-Cookie", cookie);
res.send({ status: "logged in" });
});
app.get("/resource", (req, res) => {
const authSessionCookie = req.cookies && req.cookies["AuthSession"];
if (!authSessionCookie) {
res.sendStatus(401);
return;
}
res.send({ resource: "resource" });
});
const listener = app.listen(process.env.PORT, () => {
console.log("Your app is listening on port " + listener.address().port);
});
This is the cookie sent back by the /session API:
Angular code as follows:
export class AppComponent {
constructor(private readonly httpClient: HttpClient) {
this.httpClient
.post("https://uneven-glowing-scorpion.glitch.me/session", {})
.subscribe(resp => {
console.log(resp);
this.httpClient
.get("https://uneven-glowing-scorpion.glitch.me/resource")
.subscribe(resp => {
console.log(resp);
});
});
}
}
As you can see the server is available at https://uneven-glowing-scorpion.glitch.me for testing purposes.
http://localhost:4200 and https://uneven-glowing-scorpion.glitch.me are not the same domain, therefore no cookie gets sent. It's really just that simple. There is no way around that with cookies. Cookies cannot cross domains. That's what makes them secure.
The reason your HTTP call works with Postman is because that application is very forgiving in these situations; browsers are not. There are many questions about this on SO.
Related
I'm building a React-Node app to consume QuickBooks APIs using OAuth 2 authentication. The app is structured so that the react app runs off a dev server at localhost:3000, and proxies http requests to the express server at localhost:3001.
So, I'm having some trouble making API calls: the react component responsible for rendering API data is crashing, and I'm getting the following error
"Missing required parameter: access_token"
I have the following code in my express server, which converts the authorization code into an access token, and then (I think) passes that token to http://localhost:3000/companyInfo. However I suspect this is where the problem is - is the token actually being sent to this address, or have I misunderstood how OAuth works? Here's the server-side code in question:
app.get("/callback", function (req, res) {
oauthClient
.createToken(req.url)
.then(function (authResponse) {
oauth2_token_json = JSON.stringify(authResponse.getJson(), null, 2);
})
.catch(function (e) {
console.error(e);
});
res.redirect("http://localhost:3000/companyInfo" );
});
...here's my entire server:
const express = require("express");
const OAuthClient = require("intuit-oauth");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const port = process.env.PORT || 3001;
let urlencodedParser = bodyParser.urlencoded({ extended: true });
let oauth2_token_json = null;
let oauthClient = null;
app.get("/authUri", urlencodedParser, (req, res) => {
oauthClient = new OAuthClient({
clientId: "****",
clientSecret: "****",
environment: "sandbox",
redirectUri: "http://localhost:3001/callback",
});
let authUri = oauthClient.authorizeUri({
scope: [OAuthClient.scopes.Accounting],
state: "testState",
});
res.send(authUri);
});
app.get("/callback", function (req, res) {
oauthClient
.createToken(req.url)
.then(function (authResponse) {
oauth2_token_json = JSON.stringify(authResponse.getJson(), null, 2);
})
.catch(function (e) {
console.error(e);
});
res.redirect("http://localhost:3000/companyInfo" );
});
app.get("/getCompanyInfo", (req, res) => {
let companyID = oauthClient.getToken().realmId;
let url =
oauthClient.environment == "sandbox"
? OAuthClient.environment.sandbox
: OAuthClient.environment.production;
oauthClient
.makeApiCall({
url: url + "v3/company/" + companyID + "/companyinfo/" + companyID,
})
.then(function (authResponse) {
console.log(
"The response for API call is :" + JSON.stringify(authResponse)
);
res.send(JSON.parse(authResponse.text()));
})
.catch(function (e) {
console.error(e);
});
});
app.listen(port, () => {
console.log(`Server is listening on port: ${port}`);
});
...and here's the react component where I want to render the returned API call data:
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const CompanyInfo = () => {
const [ info, setInfo ] = useState('')
useEffect(() => {
axios.get('/getCompanyInfo')
.then(res => setInfo(res.data))
},[])
return(
<div>
<p> {info.CompanyInfo.CompanyName} </p>
</div>
)
}
export default CompanyInfo
The strange this is that sometimes I have been able to render the API call data in this component, but I can only do it once. If I refresh my page, it crashes, and I have to start the login process again in order to make the API call work.
i get a Cannot GET /api/auth/signup error every time i try to POST i cant figure it out why
can someone help me with this thanks very much i dont understand what im missing im tryning to post the register in the node js but it keeps telling me Cannot GET /api/auth/signup
auth.js
import axios from "axios";
const API_URL = "http://localhost:8080/api/auth/";
class AuthService {
async login(username, password) {
console.log(username, password);
return axios
.post(API_URL + "signin", {
username,
password,
})
.then((response) => {
if (response.data.accessToken) {
localStorage.setItem("user", JSON.stringify(response.data));
}
return response.data;
});
}
async logout() {
localStorage.removeItem("user");
}
async register(username, email, password) {
return axios.post(API_URL + "signup", {
username,
email,
password,
});
}
getCurrentUser() {
return JSON.parse(localStorage.getItem("user"));
}
}
export default new AuthService();
server.js
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const app = express();
var corsOptions = {
origin: "http://localhost:8080",
};
app.use(cors(corsOptions));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// simple route
app.get("/", (req, res) => {
res.json({ message: "it works." });
});
// set port, listen for requests
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}.`);
});
It appears you are attempting to POST to a route that doesn't exist. You will need to build out your /api/auth/signup route in order to send a request to it from axios.
// simple example of post route
app.post("/api/auth/signup", (req, res) => {
const { username, email, password } = req.body;
// Your logic to register user in your database
// Send response after database interaction
res.status(201).json({ message: 'User signed up for service' })
});
For each Specific route you need to implement respective route in your application.
In your application, if you need to have access to two post request of different url, you need to handle two post route.
For`/api/auth/signin`
app.post('/api/auth/signin', (req, res)=>{
//Your Implementation here
res.status(201).json({message: 'Your Message'})
})
For`/api/auth/signup`
app.post('/api/auth/signup', (req, res)=>{
//Your Implementation here
res.status(201).json({message: 'Your Message'})
})
I am using node and express with vue. I try to implement a middleware for my node routes, to check the login token and if it is wrong, redirect to the login page.
This is my code so far.
Main app.js file
const express = require('express');
const path = require('path');
const cors = require('cors');
const http = require('http');
const bodyParser = require('body-parser');
const app = express();
app.use(cors());
app.use(express.static(path.join(__dirname,'public')));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended:false}));
app.use(express.json());
app.use(express.urlencoded({extended:false}));
app.use('/login', require('./routes/login'));
app.use('/performance', require('./routes/performance'));
const port = 4000;
http.createServer(app).listen(port, ()=>{
console.log('server runs on port ', port);
});
Then in my routes I have
const express = require('express');
const token = require('../../lib/token');
router.get('/:product/dataquality/', token.validate(), (req, res)=>{
const product = req.params.product;
db.dataquality(product)
.then((resolved)=>{
console.log('route dataquality result', resolved);
res.json({success:true, data:resolved});
})
.catch((error)=>{
console.log('route dataquality error', error);
res.json({success:false, data:false});
});
});
And this token.validate middleware is
const jwt = require('jsonwebtoken');
const config = require('../db/config');
const validate = ()=>{
return (req, res, next)=>{
const token = req.get('Authorization');
const key = config.key;
jwt.verify(token, key,function(err, decoded) {
if (err){
console.log('token error');
res.redirect('/login');
}
if (decoded){
console.log('token ok');
next();
}
});
}
}
exports.validate = validate;
This works fine and I see the token console logs when I login in my app. If I go to the browser and edit the token (delete a couple of characters, but not completely remove it) so the token is there but is the wrong token, it has the wrong value :
I see in my console token error and I get no data, but the redirect to login page never happens.
If I do res.redirect('http://localhost:8080/login'); I still get no redirect.
What am I missing? How can I fix this?
If you think this is a Vue issue, let me know, so I can provide Vue routing code.
Thanks
EDIT
This the file that handles routes in Vue - router.js
import Vue from 'vue'
import Router from 'vue-router'
import store from '#/components/store';
import Login from './components/Login.vue'
import Performance from './views/Performance.vue'
Vue.use(Router)
const router = new Router({
mode: "history",
base: process.env.BASE_URL,
routes: [
{
path: '/login',
name: 'login',
component: Login,
},
{
path: '/performance',
name: 'performance',
component: Performance,
beforeEnter: (to, from, next) => {
if (store.state.login.token == null || !store.state.login.token) {
next('/login');
}
else{next();}
}
},
{
path: '*',
redirect: '/login'
}
]
})
router.beforeEach((to, from, next) => {
store.dispatch('resetAccessToken');
if (to.fullPath === '/performance') {
if (!store.state.login.token) {
console.log('VUE route go to login');
next('/login');
}
}
if (to.fullPath === '/login') {
if (store.state.login.token) {
console.log('VUE route browse normally');
next('/performance');
}
}
next();
});
If I search for "route" in my browser console to find console.log('VUE route go to login'); or console.log('VUE route browse normally'); it says it found 160 but I cannot see none.
I still get no data, but I can browse normally to empty Vue pages. Node or Vue redirect should work and send me to login , but none works, so I still can browse around.
Thanks
I may be misunderstanding here.
I have a node server running at localhost:3000, and a React app running at localhost:8080.
The React app is making a get request to the node server - my server code for this looks like:
const cookieParser = require('cookie-parser');
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(cookieParser());
app.get('/', function (req, res) {
let user_token = req.cookies['house_user']; // always empty
if (user_token) {
// if the token exists, great!
} else {
crypto.randomBytes(24, function(err, buffer) {
let token = buffer.toString('hex');
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080');
res.cookie('house_user', token, {maxAge: 9000000000, httpOnly: true, secure: false });
res.send(token);
});
}
});
app.listen(3000, () => console.log('Example app listening on port 3000!'))
I'm trying to set the house_user token, so that I can later keep track of requests from users.
However, the token is not being set on the user (request from localhost:8080) - the house_user token is always empty (in fact, req.cookies is entirely empty). Do I need to do something else?
I just tried the code below (and it worked). As a reminder, you can just paste this in myNodeTest.js, then run node myNodeTest.js and visit http://localhost:3003. If it does work, then it probably means you're having CORS issues.
[EDIT] withCredentials:true should do the trick with axios.
axios.get('localhost:3000', {withCredentials: true}).then(function (res) { console.log(res) })
const express = require('express')
const cookieParser = require('cookie-parser')
const crypto = require('crypto');
const port = 3003
app.use(cookieParser());
app.get('/', function (req, res) {
let user_token = req.cookies['house_user']; // always empty
if (user_token) {
// if the token exists, great!
} else {
crypto.randomBytes(24, function(err, buffer) {
let token = buffer.toString('hex');
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080');
res.cookie('house_user', token, {maxAge: 9000000000, httpOnly: true, secure: true });
res.append('Set-Cookie', 'house_user=' + token + ';');
res.send(token);
});
}
});
app.get('/', (request, response) => {
response.send('Hello from Express!')
})
app.listen(port, (err) => {
if (err) {
return console.log('something bad happened', err)
}
console.log(`server is listening on ${port}`)
})
Making my comment into an answer since it seemed to have solved your problem.
Since you are running on http, not https, you need to remove the secure: true from the cookie as that will make the cookie only be sent over an https connection which will keep the browser from sending it back to you over your http connection.
Also, remove the res.append(...) as res.cookie() is all that is needed.
In a universal app, i loose every users cookies on a server/server http request.
I have build a small nodeJS app that reproduce the thing:
const fetch = require('isomorphic-fetch');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const server = require('express')();
server.use(bodyParser());
server.use(cookieParser());
server.use(session({
secret: 'foo',
httpOnly: false
}));
server.get('/set-cookies', (req, res) => {
req.session.user = { name: 'john doe' };
res.send('OK');
});
server.get('/a', (req, res) => {
console.log('COOKIE IN A = ', req.cookies); // Cookies are here !
const options = { credentials: 'include' };
fetch('http://localhost:3131/b', options)
.then( () => {
res.send('OK');
});
});
server.get('/b', (req, res) => {
console.log('COOKIES IN B = ', req.cookies); // Cookies are lost ! How to get it back ?
res.sendStatus(200);
});
server.listen(3131);
1) Hit GET /set-cookies
2) Hit GET /a (the cookies are here as expected)
Issue: When the /a controller will make an AJAX request to GET /b, it won't transfer the cookies, so the the route /b is unable to authenticate the user
How to transfer the users cookies on every requests ?
I have heard about "cookie jar" but i couldn't explain clearly what it is, and i didn't find any clean explanation on the web, if someone could share some knowledges about that, it would be great !
whatwg-fetch has option to send cookies by the following, but it doesn't seem to work.
fetch('http://localhost:3131/b', {
credentials: 'same-origin'
});
You can send manually cookie to the fetch by the following way.
server.get('/a', (req, res) => {
console.log('COOKIE IN A = ', req.cookies); // Cookies are here !
const options = {
'headers' : {
'Cookie': req.headers.cookie
}
};
fetch('http://localhost:3131/b', options)
.then( () => {
res.send('OK');
});
});