transfer cookies on a server/server request - node.js

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');
});
});

Related

How to authenticate keycloak token using node js that calls postgraphile?

I'm new on node js, and the company that i work for needs a proof of concept about postgraphile, the situation is this:
I created a node js mini server that uses postgraphile to access the data on postgres
The mini server works fine and can return data and also can use mutations.
I used keycloak-connect to try to access keycloak to authenticate the token from the request that is sent by postman but there is a problem.
If the token is valid or not it does not matter for the mini server, the only thing that seems to matter is that is a bearer token.
I tried to use other plugins (like keycloak-nodejs-connect, keycloak-verify, etc) but the result is the same, i also changed my code to use the examples in the documentation of those plugins but nothing.
This is my code: (keycloak-config.js file)
var session = require('express-session');
var Keycloak = require('keycloak-connect');
let _keycloak;
var keycloakConfig = {
clientId: 'type credential',
bearerOnly: true,
serverUrl: 'our company server',
realm: 'the test realm',
grantType: "client_credentials",
credentials: {
secret: 'our secret'
}
};
function initKeycloak(){
if(_keycloak){
console.warn("Trying to init Keycloak again!");
return _keycloak;
}
else{
console.log("Initializing Keycloak...");
var memoryStore = new session.MemoryStore();
_keycloak = new Keycloak({store: memoryStore}, keycloakConfig);
return _keycloak;
}
}
function getKeycloak(){
if(!_keycloak){
console.error('Keycloak has not been initialized. Please called init first');
}
return _keycloak;
}
module.exports = {
initKeycloak,
getKeycloak
};
My Index.js file:
const express = require('express')
const bodyParser = require('body-parser')
const postgraphile = require('./postgraphile')
const app = express()
const keycloak = require('../config/keycloak-config').initKeycloak()
var router = express.Router();
app.set( 'trust proxy', true );
app.use(keycloak.middleware());
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(postgraphile);
app.get('/', keycloak.checkSso(), (req, res) => {
res.send('success');
} );
var server = app.listen(8080, () => console.log(`Server running on port ${8080}`));
Also I used this code to get the token and use the keycloak-verify plugin but got nothing:
router.get('/',keycloak.protect(),function(req, res, next) {
var token=req.headers['authorization'];
console.log(token);
try {
let user = keycloak.jwt.verify(token);
console.log(user.isExpired());
} catch (error) {
console.error(error);
}
})
I know that I lack the knowledge because I am a backend (C#) developer, can somebody help me with this?, thanks in advance.
I found the answer to my problem:
const express = require("express");
const request = require("request");
var keycloakConfig = require('../AuthOnly/config/keycloak-config').keycloakConfig;
const postgraphile = require('./postgraphile');
const app = express();
const keycloakHost = keycloakConfig.serverUrl;
const realmName = keycloakConfig.realm;
// check each request for a valid bearer token
app.use((req, res, next) => {
// assumes bearer token is passed as an authorization header
if (req.headers.authorization) {
// configure the request to your keycloak server
const options = {
method: 'GET',
url: `${keycloakHost}/auth/realms/${realmName}/protocol/openid-connect/userinfo`,
headers: {
// add the token you received to the userinfo request, sent to keycloak
Authorization: req.headers.authorization,
},
};
// send a request to the userinfo endpoint on keycloak
request(options, (error, response, body) => {
if (error) throw new Error(error);
// if the request status isn't "OK", the token is invalid
if (response.statusCode !== 200) {
res.status(401).json({
error: `unauthorized`,
});
}
// the token is valid pass request onto your next function
else {
next();
}
});
} else {
// there is no token, don't process request further
res.status(401).json({
error: `unauthorized`,
});
}});
app.use(postgraphile);
app.listen(8080);

I see cookies in storage tab in my browser. But when I console log them I always see undefined

I only see cookies storage tab but cannot access them via my code
When I console.log this token and id it results in "undefined" but in my browser storage cookies are shown
const cookieParser = require('cookie-parser')
const express = require('express');
const app = express()
app.use(cookieParser())
app.get('/', async (req, res) => {
function getLinkedinId(req) {
return new Promise((resolve, reject) => {
const url = 'https://api.linkedin.com/v2/me';
const headers = {
'Authorization': 'Bearer ' + req.cookies.token,
'cache-control': 'no-cache',
'X-Restli-Protocol-Version': '2.0.0'
};
request.get({url: url, headers: headers}, (err, response, body) => {
if(err){
reject(err);
}
resolve(JSON.parse(body).id);
})
})
}
const id = await getLinkedinId(req);
res.cookie('id', id)
console.log(`id: ${req.cookies.id}`)
res.redirect('http://localhost:8080/about')
});
This is because you won't have that cookie coming from the client right after you're setting it. The concept is that you set a cookie on a response, and when the client sends another request it would attach the cookie to that.
In your case the cookie would be available on the subsequent requests, imagine this scenario: Someones logs in, the response will have a cookie attached and on the subsequent call to a private page the cookie gets evaluated:
app.post("/login", (req, res) => {
res
.writeHead(200, {
"Set-Cookie": "token=encryptedstring; HttpOnly",
"Access-Control-Allow-Credentials": "true"
})
.send();
});
app.get("/private", (req, res) => {
if (!req.cookies.token) return res.status(401).send();
res.status(200).json({ secret: "Welcome to the private page" });
});

Why is cookie not getting sent to server

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.

html file does not appear after logging without any error

I am new to server-side programming.
I am trying to serve a HTML file (mapview.html) after authentication ,but it does not appear without any error.
there is no problem with authentication process. I expect when I click on login button, the codes check req data
and after validating, mapview.html pop up but nothing happen.
res.sendFile() causes in jquery part, console.log(res), get me all html codes in console line of chrome.
files directory:
src
index.js
db
public
index.html
mapview.html
middleware
auth.js
routers
user
task
model
user
task
index.html
$('div[name="logIn"]').click( () => { // select a div element that its name is logIn
console.log('LOG-IN:')
$.ajax({
url: '/login',
data: $('#loginForm').serialize(), // Get email and pass from login form
type: 'POST',
success: function (res) {
console.log(res)
}
})
})
user.js
router.post('/login', async (req, res) => {
try {
const user = await User.findByCredentials(req.body.email, req.body.password)
const token = await user.generateAuthToken()
const publicDir = path.join(__dirname, '../public')
res.sendFile(path.join(publicDir + '/mapview.html'));
} catch (e) {
res.status(400).send(e)
}
})
index.js
const express = require('express');
require('./db/mongoose');
const bodyParser = require('body-parser');
const userRouter = require('./routers/user');
const taskRouter = require('./routers/task');
const app = express();
const port = process.env.PORT || 3000;
app.use(express.json()); // Parse recieved json body data from Postman
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.use(express.static(__dirname + '/public'));
app.use(userRouter);
app.use(taskRouter);
app.listen(port, () => {
console.log('server is run on port:' + port)
});
when you are making the HTTP post request from index.html using ajax to verify user authentication details, on successful authentication you are sending a static html file which is simply a text response and will not be rendered as a web page (as you were expecting).
To solve this problem,
Create a separate route for accessing the mapview.html
app.get('/map', (req, res) => {
res.sendFile(__dirname + '/public' + '/mapview.html');
});
In your ajax response just redirect to the map route
$.ajax({
url: '/login',
data: $('#loginForm').serialize(), // Get email and pass from login form
type: 'POST',
success: function (res) {
console.log(res);
window.location.href = '/map'; // redirect to map route
}
});
I updated the codes and it looks like following code .
As I mention in the past comment , I need to authenticate before refirect through .href = '/map' and I do not know how to attach token to .href='/map.
we usually send token as header with ajax like this:
headers:{"Authorization": localStorage.getItem('token')}
in case I add it to .href ,something like this window.location.href = '/map + "?token=MY_TOKEN" , how can i get it in auth method?
user.js
router.post('/login', async (req, res) => {
try {
const user = await User.findByCredentials(req.body.email,
req.body.password)
const token = await user.generateAuthToken()
res.send({user, token})
} catch (e) {
res.send(e)
res.status(400).send(e)
}
})
router.get('/map', auth, (req, res) => {
const publicDir = path.join(__dirname, '../public')
res.sendFile(path.join(publicDir + '/mapview.html'));
});
index.html
$('div[name="logIn"]').click( () => {
$.ajax({
url: '/login',
data: $('#loginForm').serialize(),
type: 'POST',
success: function (res) {
localStorage.setItem('token', res.token);
window.location.href = '/map';
}
})
})

Node Express setting cookies

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.

Resources