req.cookies get overwritten when make request using nextjs - node.js

I'm trying to make a request to the express/nodejs backend using nextjs
in pages/reader.js, I have
Reader.getInitialProps = async ({query}) => {
const res = await fetch('http://localhost:3000/api/books/reader/' + query.id);
const json = await res.json();
return {book: json}
};
Unfortunately, that overwrites the cookies stored in the request object on the backend. When I do a console.dir(req.cookies) in the backend node js, express code, I get undefined in book.js where the reader code is.
How can I fetch without overwriting the request object in the express backend?

Look at the example in https://github.com/zeit/next.js/blob/canary/examples/auth0
In the file ssr-profile.js it shows how you can forward your cookies in the request to the server:
// To do fetches to API routes you can pass the cookie coming from the incoming request on to the fetch
// so that a request to the API is done on behalf of the user
// keep in mind that server-side fetches need a full URL, meaning that the full url has to be provided to the application
const cookie = req && req.headers.cookie
const user = await fetchUser(cookie)

Related

jwt accessing private page after login works on postman, but not on ejs view

I have the backend of a little register/login project on node, which works fine on postman, I'm doing the frontend using just ejs views, the registration works fine and the login alone too, but if I go to the private page, that works with the jwt token, it doesn't find the token I supposedly got when logged in, console says it's undefined.
This is the verification code.
const jwt = require('jsonwebtoken');
module.exports = function (req,res,next){
const token = req.header('auth-token');
console.log(token);
if(!token) return res.status(401).send('access denied');
try {
const verified = jwt.verify(token,process.env.TOKEN_SECRET);
req.user = verified;
//see private content
next();
} catch (err) {
res.status(401).send('invalid token');
}
}
this is the backend of the posts page
const router = require('express').Router();
const verify = require('./verifyToken');
//the verify marks this content as private
router.get('/',verify,(req,res)=>{
res.render('posts.ejs');
});
module.exports = router;
On postman I fill the token name on the headers, but how can I do something like this on the actual thing?
I searched a bit on this and we cannot pass headers to a url.
You can check out this question
Adding http request header to a a href link
When doing an ajax request however we can do attach custom headers and all that. We have full control over the request. I will advise you to use sessions in place of jsonwebtokens. Json Web Tokens are mostly used when using a Front End Framework React, Angular etc because we have to make ajax requests. We than save the token in localStorage and send the token in every subsequent request in the header.

NextJS - Sending cookies to API server from getInitialProps

I'm using NextJS as a client side repository to talk to my backend API server (laravel). Auth is done via JWT stored in the cookies. It all works seamlessly when I send an auth request to https://my-backend-server.com/api/login, as I respond with a cookie, and set to the my-backend-server.com domain. And even when I send requests from the browser.
Problems arise when I want to load the page, and sent the request from getInitialProps, as this is a serverside call. How am I able to access the cookies to my-backend-server.com, and put those in the header, so the server-side request from NextJS is properly authorized?
Most of the answers say something about req.cookies or req.headers.cookies, however this is empty as the request in getInitialProps is to http://my-local-clientside-site.com
As you explained correctly, Next's getInitialProps is called on client & on server.
If your Next app & Api services are served from the same domain, your Api service can put the cookie on the domain, and it will work on the client side (browser).
Whenever your Next app accessing the api from server-side, you need to attach the cookie by yourself.
getInitialProps method on the server side gets on the context (first param) the request (as req) from the browser, that means that this request has the cookie.
If you have a custom server, you probably need add to it a cookieParser,
// server.js
import cookieParser from 'cookie-parser';
import express from 'express';
import next from 'next';
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
app.prepare().then(() => {
const nextHandler = app.getRequestHandler();
const server = express();
server.use(cookieParser());
// -----------^
server.get('*', (req, res) => nextHandler(req, res));
});
This parser will parse the Cookie header and put it as an object on the req.
After that, your req.cookie should have the cookie value (make sure that you see that the browser sends it in the document request) you can access it in your getInitialProps,
//pages/index.js
const IndexPage = () => <div>BLA</div>
IndexPage.getInitialProps = (context) => {
if(context.req) {
// it runs on server side
axios.defaults.headers.get.Cookie = context.req.headers.cookie;
}
};
I've given you an example that sets up axios to put the cookie on all requests that will be made from the client.
Felixmosh' answer is half correct. rather than context.req.cookie it should be context.req.headers.cookie.
const IndexPage = () => <div>BLA</div>
IndexPage.getInitialProps = (context) => {
if(context.req) {
// it runs on server side
axios.defaults.headers.get.Cookie = context.req.headers.cookie;
//make api call with axios - it would have correct cookies to authenticate your api call
}
};

How to SSO in another domain using express server/react client and redirect to another domain?

Problem is to create a web app that has a form that sends it to my express server, generates a token from external endpoint post with form value, uses token from external domain to make a post to external endpoint to then get authenticated into external domain.
I am able to generate token on my express server, make the second post with token to external endpoint and able to login and see the response on express server.
However, when I try to redirect my react client to the external domain, it shows a timeout message.
I've tried making the 2nd post from client with Axios and Fetch and then redirect to external domain, but it gives me CORS errors, until I turn on CORS chrome plugin, then it still gives me the same timeout message.
I've tried adding different headers to my post call to allow redirects, but no success.
const router = require('express').Router();
const xml2js = require('xml2js');
const parseString = require('xml2js').parseString;
const request2 = require('request');
// const axios = require('axios')
var setCookie = require('set-cookie-parser')
var Cookies = require('js-cookie')
require('dotenv').config();
router.post('/sso', (req, response, next)=>{
// SETTING UP XML BODY FOR TOKEN GENERATOR
// USING TEMPLATE TO BUILD XML BODY FOR TOKEN GENERATOR
// SETTING UP POST TO TOKEN GENERATOR WITH XML TEMPLATE
// DECLARING TOKEN TO PASS TO SSO POST
// PROMISE FOR RESPONSE POST TO TOKEN GENERATOR
return new Promise((resolve)=>{
// ERROR CATCH BLOCK FOR POST TO TOKEN GENERATOR
try {
// POST TO TOKEN GENERATOR USING XML TEMPLATE
request2(TokenGenerator,
(err, res, body)=>{
// PARSE TOKEN GENERATOR BODY RESPONSE
// CONVERTING TO STRING SOAP BODY
// PARSING STRING INTO JSON TO TARGET TOKEN
// DECLARING TOKEN RESPONSE IN RES WITH TOKEN VALUE FROM POST TO TOKEN GENERATOR
// ASSIGNING IT TO GLOBAL VARIABLE
})
// TRYING POST FROM CLIENT HAS BEEN COMMENTED OUT
// // response.send(token)
// // next()
// SETTING UP POST TO PARTICIPANT SSO WITH TOKEN VALUE
const secondPostToSSO = {
method: 'POST',
url: 'externaldomain.com/sso.aspx',
followAllRedirects: true,
jar: true,
headers: {
'Content-Type': 'text/html'
},
form: {
'TOKEN': token
}
}
// POST TO PARTICIPANT SSO WITH TOKEN
request2.post(secondPostToSSO,(err, response2, body2)=>{
console.log(response2.request)
var cookies = setCookie.parse(response2, {
decodeValues: true,
map: true
})
console.log(cookies)
next()
})
})
} catch (e) {
console.error(`[FATAL ERROR] - KAPUT - ${e}`)
return res.redirect('/')
}
})
})
module.exports = router
I expect the output of the server post to then redirect the client to the externaldomain.com where I'm getting the token, and making post with token to authenticate client. The outcome should be that the client has been logged from my web app to the external domain.
I was able to solve the CORS issue and complete the application a few weeks ago and I wanted to share my answer.
The way I solved the CORS issue was to remove React and use simple HTML client to the server side on the same port. I use the npm package cors (https://www.npmjs.com/package/cors). I added one line to the server.js file like this: app.use(cors());.
After that, I updated my SSO route to pass the token with response.send(token) instead of using next().
Once the token was received by the browser, remember, I have server and client on same port, client would trigger a hidden form POST once the token was received from the server.
VoilĂ , that solved it.
The API route:
const path = require('path');
const router = require('express').Router();
const app = require('express')
const xml2js = require('xml2js');
const parseString = require('xml2js').parseString;
const request2 = require('request');
const cors = require('cors')
require('dotenv').config();
// API routes
module.exports.routes = function (app) {
// POST API
app.post("/api/sso", (request, response, next)=>{
// SETTING UP XML BODY FOR TOKEN GENERATOR
const Template = {
// YOUR TEMPLATE
}
// USING TEMPLATE TO BUILD XML BODY FOR TOKEN GENERATOR
const builder = new xml2js.Builder()
const xml = builder.buildObject(Template)
// SETTING THE RIGHT ENVIRONMENT LINK FOR TOKEN GENERATOR BASED ON USER INPUT WITH IF STATEMENT
// SETTING UP POST TO TOKEN GENERATOR WITH XML TEMPLATE
const TokenGenerator = {
url: tokenGeneratorUrl,
method: 'POST',
headers: {
'Content-Type': 'text/xml'
},
body: xml
}
// DECLARING TOKEN TO PASS TO SSO POST
let TOKEN = ''
// PROMISE FOR RESPONSE POST TO TOKEN GENERATOR
return new Promise((resolve) => {
// ERROR CATCH BLOCK FOR POST TO TOKEN GENERATOR
try {
// POST TO TOKEN GENERATOR USING XML TEMPLATE
request2(TokenGenerator,
(err, res, body) => {
// PARSE TOKEN GENERATOR BODY RESPONSE
parseString(body,
(err, result) => {
// CONVERTING TO STRING SOAP BODY
const strBody = JSON.stringify(result['soap:Envelope']['soap:Body'])
// PARSING STRING INTO JSON TO TARGET TOKEN
// DECLARING TOKEN RESPONSE IN RES WITH TOKEN VALUE FROM POST TO TOKEN GENERATOR
// ASSIGNING IT TO GLOBAL VARIABLE
TOKEN = res.TokenResponse
})
// SENDING TOKEN TO CLIENT TO POST TO SSO
response.send(TOKEN)
})
} catch (e) {
console.error(`[FATAL ERROR] - KAPUT - ${e}`)
return res.redirect('/')
}
})
});
};
The hidden form on the client:
<form style="display: none;" novalidate autocomplete="off" name="hiddenForm" id="hiddenForm" action="https://api.endpoint.com/route" method="POST" class="hiddenForm">
<input type="hidden" id="TOKEN" name="TOKEN" value="">
</form>

Axios on Nodejs wont retain session on requested server while PostMan does

I am able to do the following on PostMan
1) POST method to login to company server.
2) Make other requests as a logged in user on company server.
I have created a nodejs app to communicate with the company server.
I am using axios library to make said communications.
after Logging in to company server, any other calls don't recognize me as an authorized user.
What could be the differences that i could in turn recreate on axios to have that session persistance?
In the browser you use withCredentials in axios - this option automatically saves your session between requests. But in node.js this parameter does not work because axios uses the http node.js module instead of XHR.
In node.js, you can use an axios instance for save cookie between requests.
Simplest way is:
Create instance
const BASE_URL = "https://stackoverflow.com";
// Create instance of axios which utilizes BASE_URL
const axiosInstance = axios.create({ baseURL: BASE_URL });
Write createSession function
const createSession = async () => {
console.log("create session");
const authParams = {
username: "username",
password: "password"
};
const resp = await axios.post(BASE_URL, authParams);
const cookie = resp.headers["set-cookie"][0]; // get cookie from request
axiosInstance.defaults.headers.Cookie = cookie; // attach cookie to axiosInstance for future requests
};
And make call with session cookie
// send Post request to https://stackoverflow.com/protected after created session
createSession().then(() => {
axiosInstance.post('/protected') // with new cookie
})
Be careful, your authorization method may differ from the presented - in this case you can just change the createSession method. If your session has expired, you can login again directly or using axios.interceptors - I attached a link to gist.
Also you can use cookie-jar with axios (link below)
For more info:
https://github.com/axios/axios#creating-an-instance
https://github.com/axios/axios#interceptors
https://gist.github.com/nzvtrk/ebf494441e36200312faf82ce89de9f2
https://github.com/3846masa/axios-cookiejar-support

NodeJS Request handler

I have some issues with a server that does not support IPv6 requests from Apple Application Review. So they reject my update.
And i'm thinking of making a request handler as a middle server, with nodejs.
So my app will send the requests in my new server, which server will send the request to the old server, take the response json back, and serve it back as well in my app.
So lets say the old webserver request was the following
https://www.example.com/example/api/index.php?action=categories&subaction=getproducts&category_id=100304&limit=0,30
But the request parameters are not always the same!
It may vary but the main URL is always the same
https://www.example.com/example/api/index.php?
The question is how to get the request params dynamically, make a request to the old webserver and return the response to the request of the new webserver?
You just need a very simple proxy like this;
const express = require('express')
const request = require('request')
const app = express()
const BASE_URL = 'http://www.google.com' // change accordingly
app.use('/', (req, res) => {
request({
url: BASE_URL + req.originalUrl
}).pipe(res)
})
app.listen(8900, () => console.log('Listening...'))
req.originalUrl will allow to concatenate the path + the query string to your base url

Resources