I cannot seem to get the seem to get the sever to send/set cookies on the client. When I open the developer and inspect the redirect, no cookies have been sent or set.
// pages/api/auth/github.ts
const handler: NextApiHandler = async (request, response) => {
// Use request.query.code to fetch access token, ect.
response.setHeader(
'Set-Cookie',
serialize('token', data.access_token, {
path: '/',
}),
);
response.redirect(`http://${request.headers.host}`);
};
I've added
{ sameSite: 'lax' }
to the cookie options and it works now.
Full working example:
// pages/api/auth/github.ts
const handler: NextApiHandler = async (request, response) => {
// Use request.query.code to fetch access token, ect.
response.setHeader(
'Set-Cookie',
serialize('token', data.access_token, {
path: '/',
sameSite: 'lax'
}),
);
response.redirect(`http://${request.headers.host}`);
};
Related
I am using nest js and want to set the cookies when the user will hit a specific endpoint:
#Get()
setCookiesApi(#Res({ passthrough: true }) response:Response) {
response.setCookie('key', 'value')
}
This code works and the cookies are set in cookies storage from the Application tab in Chrome. If i try setting cookies using post:
#Post()
setCookiesApi(#Res({ passthrough: true }) response:Response) {
response.setCookie('key', 'value')
}
My code on UI:
try {
const response = await axios.post(
`http://localhost:3000/api/v1/hello`,
user,
{
method: 'post',
headers: {
withCredentials: true,
},
data: user,
},
);
if (response.data) {
// sss
}
} catch (err) {
if (err instanceof AxiosError) {
if (err.response) {
toast.error(err.response.data.message);
}
}
}
main.js file
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api/v1');
app.useGlobalPipes(new ValidationPipe());
app.use(cookieParser());
app.enableCors({ origin: 'http://127.0.0.1:5173', credentials: true });
await app.listen(3000);
}
bootstrap();
...then the cookies storage is empty and no cookie is set. Question: Why get request works but post not and how to solve this?
I think the GET request works because it is an HTTP method that allows for retrieving data from a server, and the setCookie() method is used to set a cookie in the response.
The POST request doesn't work because POST is typically used for creating or modifying data on a server, and the setCookie() method is not designed to handle data modifications.
It works with POST method as well. Try something similar like this:
#Post()
setCookiesApi(#Res({ passthrough: true }) response:Response) {
response.setHeader('Set-Cookie', cookie);
return response.send();
}
I'm using http-proxy-middleware in NodeJS to proxy a PHP site that does authentication.
The PHP site returns a header "saml-user-id".
I use this saml-user-id to generate a token which I want to add as cookie. I need to lookup the user in the database, so it needs to work asynchronously.
The code looks like this:
// DOESN'T WORK
exports.proxySimpleSamlMiddleware = createProxyMiddleware(['/saml'], {
ssl: {
key: fs.readFileSync('/path/to/my.key'),
cert: fs.readFileSync('path/to/my.crt'),
},
target: 'https://127.0.0.1:8443',
secure: false,
logLevel: 'debug',
changeOrigin: true,
onProxyRes: async (proxyRes, req, res) => { // <-- ASYNCHRONOUS
let tokenCookie = 'token=';
if (proxyRes.headers['saml-user-id']) {
const userId = proxyRes.headers['saml-user-id'];
const token = await getNewToken(userId);
tokenCookie = `token=${token}; path=/; httponly; samesite=lax`;
}
// Set the 'token' cookie
// (simplified, in reality I read the existing cookies from the headers
// and add this cookie to the list)
proxyRes.headers['set-cookie'] = tokenCookie;
},
});
The above code doen't work. No headers are changed, and I get an error ("Cannot set headers after they are sent to the client"). I guess that this is because of the asynchronous function in onProxyRes.
If I change the code to this:
// DOESN'T WORK ASYNCHRONOUSLY
onProxyRes: (proxyRes, req, res) => { // <-- REMOVED ASYNC
let tokenCookie = 'token=my-new-token';
proxyRes.headers['set-cookie'] = tokenCookie;
},
Then the "token" cookie is set to "my-new-token" as expected. This is not an option because the change I want to make is asynchronous.
I have experimented with "responseIntercepter":
// DOESN'T WORK
selfHandleResponse: true,
onProxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
let tokenCookie = 'token=my-new-token';
proxyRes.headers['set-cookie'] = tokenCookie;
return responseBuffer;
}),
But this doesn't update the "token" cookie either.
My question:
Is there a way using http-proxy-middleware to change the headers asynchronously?
i have an app with Webserver implemented using Hapi JS, and whenever i opened an application with browser's tab i am able to see the cookies are injected in Request header.
but if i load my app inside third application through Iframe. there is missing cookies from the header . can you please some body help here .
Hapi JS code
server.auth.strategy('session', 'cookie', {
password: 'longpassword-should-be-32-characters-for-pulse',
cookie: 'my-app-sid',
redirectTo: '/',
ttl: 86400000,
isSecure: false,
validateFunc: isAuth
});
and my API call where the cookies missing from req header
getBookDetails = () => {
return {
auth: {
strategy: 'session'
},
handler: {
proxy: {
mapUri: (request, callback) => {
let url = 'https://mydemoapp.com'
let tokenHeaders = { token: request.auth.credentials.token,
assetid: request.headers['assetid'],
asseturl:request.headers['asseturl'],
deviceid: deviceid,
appversion: request.headers['appversion'],
'user-agent': request.headers['user-agent'],
'accept-language': request.headers['accept-language']
};
url = url + '/book/' + request.params.bookId;
callback(null, url, tokenHeaders);
},
onResponse: (err, res, request, reply) => {
wreck.read(res, { json: true, gzip: true }, (err, payload) => {
reply(payload);
});
}
}
}
}
}
i am suspecting that since my app is loaded inside the iframe of third party app. and while requesting the webserver URL mentioned above getBookDetails() it could'nt read the cookies by Iframe from the Parent app (Third party app).
can some one help please
I think the first place should be checked in your browser.
I'm using the http-proxy-middleware (https://github.com/chimurai/http-proxy-middleware#http-proxy-events) to implement a simple proxy (call it my-proxy/) to another REST API (call it /rest-api) that requires the user to pass an auth token in the HTTP header auth-token. The token can be fetched from an endpoint POST /rest-api/auth with the credentials in the body.
I want my proxy to take incoming requests and check if auth-token is set in the the request header, and if not perform a POST /rest-api/auth to retrieve the token and set auth-token in the header before passing the request to rest-api/.
In the proxy config I specify
onProxyReq: function (proxyReq, req, res) {
if (!req.header("auth-token")) {
const authRequest = request({
url: 'rest-api/auth',
method: 'POST',
json: {"username": "user", "password": "pass"}
},
function (error, resp, body) {
proxyReq.setHeader("auth-token", body.token)
}
);
}
}
I can see the body.token return the right token. However the setHeader call fails with Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client.
I think this means the request I modify has already been sent to rest-api/ before waiting for the callback, but I don't know how this is best solved in my scenario.
Any help?
I met this same issue today. I workaround this by using a separate middleware (before http proxy).
pseudo code
// fix token stuff here in a separate middleware
app.use('/api', async (req, res, next) => {
if (!req.session.token) {
const resToken = await axios.post(token_url, data, options)
req.session.token = resToken.data
}
next()
}
// proxy middleware options
const proxyOptions = {
target: config.gateway.url, // target host
changeOrigin: true,
pathRewrite: function(path, req) {
return path.replace('/api', '')
},
onProxyReq: function onProxyReq(proxyReq, req, res) {
// add custom header to request
let token = req.session.token
if (token) {
proxyReq.setHeader('Authorization', `bearer ${token.access_token}`)
}
},
logLevel: 'debug',
}
app.use(proxy('/api', proxyOptions))
Hope this helps!
TL;DR
How do I intercept a request, ping a different route for an ID, store the ID in a session, then continue the original request (especially PUT/POST with payloads) using the ID I just got?
Background
I am using HapiJS (8) to proxy requests from the client to an existing API (where I have no control over processes/logic). The API requires each request to contain a 'session ID' in either the query string or the payload (depending on the http method). In order to get a session ID, all I have to do is ask for one... there is no username/pwd required (it uses Basic auth in the headers). The session ID expires every 24 hours if it isn't renewed. Each client has their own session ID.
I am currently using hapi-auth-cookie to store the value of the session ID which gets queried when the ID is needed. If the ID is expired or null, I need to request a new one before the client's request can be successfully proxy'd to the API.
Current Solution
When the client's request method is 'GET', I am handling this challenge quite gracefully utilizing the appendNext configuration described in the hapi-auth-cookie docs. The request is intercepted by hapi-auth-cookie, if a new session ID is needed, a request is sent to that particular route to get it, the API returns the ID which is then assigned to the Hapi session, and then (using Wreck) a reply.redirect returns to the original GET request where it completes. Seamless and graceful.
However, I cannot figure out how to accomplish this same flow with different http methods that contain payloads of data.
Is there something besides reply.redirect that will accomplish the same goal while maintaining original payloads and methods? Or is there a much better way to do this in general?
The Code (for what currently works for 'GET' requests)
main app file (hapi-auth-cookie configs)
# register plugins
server.register require('./plugins')
, (err) ->
throw err if err
# set authentication (NLS) configs
server.auth.strategy 'session', 'cookie',
password: 'session_pwd'
cookie: 'ghsid'
redirectTo: '/api/SessionID' #get new session ID
isSecure: config.get 'ssl'
appendNext: true
ttl: config.get 'session_length'
Controller that leverages session authentication and invokes hapi-auth-cookie plugin:
simpleRequest:
auth: 'session'
handler: (request, reply) ->
qs = Qs.stringify request.query
request.papi_url = "/api/route/sample?#{qs}"
reply.proxy
mapUri: (request, reply) ->
auth = config.get 'basic_auth'
api_host = config.get 'api_host'
papi_url = request.papi_url
path = api_host + papi_url
next null, path, {authorization: auth}
Route for getting a new session ID
module.exports = [
{
path: '/api/SessionID'
method: 'GET'
config: SessionController.session
}
]
Session Controller
Wreck = require 'wreck'
config = require 'config'
module.exports =
session:
description: 'Get new session ID'
auth:
mode: 'try'
strategy: 'session'
plugins:
'hapi-auth-cookie':
redirectTo: false
handler: (request, reply) ->
# request configs
papi_url = "/Session"
api_host = config.get 'api_host'
url = api_host + papi_url
opts =
headers:
'Authorization': config.get 'basic_auth'
'content-type': 'application/json;charset=UTF-8'
# make request to PAPI
Wreck.post url, opts, (err, res, body) ->
throw new Error err if err
try
bdy = JSON.parse body
sess =
nls: bdy.SessionId
if bdy.SessionId
# authenticate user with NLS
request.auth.session.set sess
# redirect to initial route
reply.redirect request.url.query.next
else
return throw new Error
catch err
throw new Error err
Final solution
Based on Matt Harrison's answer, I created a custom plugin that gets registered as an authentication scheme so I can control this per route.
Here's the plugin code:
Wreck = require 'wreck'
config = require 'config'
exports.register = (server, options, next) ->
server.auth.scheme 'cookie', internals.implementation
next()
exports.register.attributes =
name: 'Hapi Session Interceptor'
version: '1.0.0'
internals = {}
internals.implementation = (server, options, next) ->
scheme = authenticate: (request, reply) ->
validate = ->
session = request.state.sessionID
unless session
return unauthenticated()
reply.continue(credentials: {'session': session})
unauthenticated = ->
api_url = "/SessionID"
api_host = config.get 'api_host'
url = api_host + api_url
opts =
headers:
'Authorization': config.get 'basic_auth'
'content-type': 'application/json;charset=UTF-8'
# make request to API
Wreck.post url, opts, (err, res, body) ->
throw new Error err if err
bdy = JSON.parse body
sess =
session: bdy.SessionId
if bdy.SessionId
reply.state 'sessionID', bdy.SessionId
reply.continue(credentials: sess)
else
return throw new Error
validate()
return scheme
Although not entirely faithful to your code, I've put together an example that has all the pieces I think you're working with.
I've made a service plugin to represent your API. The upstream plugin represents the actual upstream API you're proxying to.
All requests passthrough the service and are proxied to the upstream, which just prints out all of the headers and payload it received.
If the original request doesn't contain a cookie with a sessionId, a route is hit on the upstream to get one. A cookie is then set with this value when the response comes back down stream.
The code is here: https://github.com/mtharrison/hapijs-proxy-trouble
Try it out with curl and your browser.
GET: curl http://localhost:4000
POST W/PAYLOAD: curl -X POST -H "content-type: application/json" -d '{"example":"payload"}' http://localhost:4000
index.js
var Hapi = require('hapi');
var server = new Hapi.Server();
server.connection({ port: 4000, labels: ['service'] }); // Your service
server.connection({ port: 5000, labels: ['upstream']}); // Pretend upstream API
server.state('session', {
ttl: 24 * 60 * 60 * 1000,
isSecure: false,
path: '/',
encoding: 'base64json'
});
server.register([{
register: require('./service')
}, {
register: require('./upstream')
}],
function (err) {
if (err) {
throw err;
}
server.start(function () {
console.log('Started!');
});
});
service.js
var Wreck = require('wreck');
exports.register = function (server, options, next) {
// This is where the magic happens!
server.select('service').ext('onPreHandler', function (request, reply) {
var sessionId = request.state.session;
var _done = function () {
// Set the cookie and proceed to the route
request.headers['X-Session-Id'] = sessionId;
reply.state('session', sessionId);
reply.continue();
}
if (typeof sessionId !== 'undefined')
return _done();
// We don't have a sessionId, let's get one
Wreck.get('http://localhost:5000/sessionId', {json: true}, function (err, res, payload) {
if(err) {
throw err;
}
sessionId = payload.id;
_done();
});
});
server.select('service').route({
method: '*',
path: '/{p*}', // Proxies all routes and methods
handler: {
proxy: {
host: 'localhost',
port: 5000,
protocol: 'http',
passThrough: true
}
}
});
next();
};
exports.register.attributes = {
name: 'your-service'
};
upstream.js
exports.register = function (server, options, next) {
server.select('upstream').route([{
method: '*',
path: '/{p*}',
handler: function (request, reply) {
// Just prints out what it received for headers and payload
// To prove we got send the original payload and the sessionID header
reply({
originalHeaders: request.headers,
originalPayload: request.payload,
})
}
}, {
method: 'GET',
path: '/sessionId',
handler: function (request, reply) {
// Returns a random session id
reply({ id: (Math.floor(Math.random() * 1000)) });
}
}]);
next();
};
exports.register.attributes = {
name: 'upstream'
};