Why can't I access this cookie?
When the user logs in the cookie is recieved and sent back to the Express server.
When initializing a new websocket to the Socket.io server this cookie does not get sent, so I was trying to get it via the document.cookie. However, it did not work since the the cookie was not modifiable.
It is an HttpOnly cookie that cannot be accessed via client-side Javascript.
In other words: The server is able to read and manipulate the cookie. The client receives it and blindly sends it back with every subsequent request, without being able to read or manipulate its contents, at least not with Javascript means.
The official website did something that did not work for me.
express session middleware
const session = require("express-session");
io.use(wrap(session({ secret: "cats" })));
io.on("connection", (socket) => {
const session = socket.request.session;
});
So, I got around it by making:
Logic:
Before establishing websocket, request credentials to express endpoint "/userCredentials"
and then use these credentials to establish the connection
Warning: Down below code is stripped because I did so many auth logics
CLIENT:
...
useEffect(() => {
(async() => {
const pending_creds = await fetch("/userCredentials");
const creds = pending_creds.json();
const ws = io({auth: {creds}})
setSocket(ws)
})()
}, [])
...
SERVER:
...
app.get("/userCredentials", (req,res) => {
const userSession = req.session.user;
res.json(userSession)
})
...
io.use(socket, next){
const creds = socket.handshake.auth.userSession;
if(creds){
next()
} else {
socket.disconnect()
}
}
Related
Considering the following code:
const { SAMLResponse } = qs.parse(context.req.body)
const { clientName } = context.req.params
try {
const samlResponseFormatted = decodeURIComponent(
SAMLResponse as string
)
logger.add(`SAML response formatted: ${samlResponseFormatted}`)
const auth = await new SsoAuthenticate().getAuth(
samlResponseFormatted,
clientName
)
const expirationDate = new Date(Date.now())
expirationDate.setHours(expirationDate.getHours() + 24)
logger.add(`Token cookie expiration date set to: ${expirationDate}`)
const headers = {
Location: `${auth?.redirectUrl}`,
"Set-Cookie": `token=${
auth?.token
}; Expires=${expirationDate.toUTCString()};`,
}
logger.add(
`Answering the AuthSSO request with the following headers: ${JSON.stringify(
headers
)}`
)
context.res = {
status: StatusCodes.MOVED_TEMPORARILY,
headers,
}
}
I can't see the cookie or the header after the redirect, and I need to pass the token to such redirected URL without passing the token as query parameters (Unsafe). I'm lost and probably this is not the right approach, but is it possible to send the token together with the redirect? If it's not, is there a better approach?
In here they say that the better approach is to use cookies instead of just passing the token on the authorization header, but it's not working for me
EDIT: After researching a bit I've realized that the Set-Cookie is not working because of a cross-domain redirect (302). I need to think of an alternative way of doing this.
To redirect you can use an express app on top of the azure function where all the traffic would be diverted to the express app. Basically the express app will be written in the index.js file of the httptrigger and will act like function.
This is achieved using an npm package
azure-aws-serverless-express
code:
const express = require('express');
const azureFunctionHandler = require('azure-aws-serverless-express');
const app = express();
app.get('/api/hello-world/', (req, res) =>
{
res.cookie('function','express');
res.redirect('https://www.google.com/');
res.send("Hello World");
}
);
module.exports = azureFunctionHandler(app);
This way now you can redirect to a different url .
Also, you won't be able to set a cookie and redirect at the same time as the cookie you are setting will be related to a domain which itself is getting redirected.
Refer this answer by jfriend00 for detailed explanation.
The browser won't let you access the cookies as it is not of same domain
So a compromise would be to pass whatever you want in the url itself as a variable
var token = "SAS_TOKEN"
res.redirect('https://www.google.com/${token}');
You can refer this article by Pradeep Moolmane on cross domain cookies, but it uses XMLHttpRequest
On the frontend/client-side, I have an implementation where I secure my socket.io connection using auth token. This auth token is updated in the store every few minutes.
const authToken = getToken()
socket = io(WS_BASE_URL, {
auth: {
token: authToken
}
});
In this current implementation, when the socket tries to reconnect, authToken is not updated. How to dynamically set it every time reconnect attempt is made?
socket = io(WS_BASE_URL, {
auth: {
token: () => getToken()
}
});
I want this kind of implementation where I pass a function to get the token every time from the store. Or is there any way we can modify handshake data in the reconnect_attempt event?
socket.io.on("reconnect_attempt", () => {
// update handshake
});
socket.io 2.x client api document has this
"The query content can also be updated on reconnection:"
socket.on('reconnect_attempt', () => {
socket.io.opts.query = {
token: 'fgh'
}
});
I think you use 4.x because you use auth option, but I think it is basically the same. The middleware at server side will get the update token when a socket reconnect.
I am configuring a server using express.
My question has nothing to do with the project itself because it is running great.
I just have a minor doubt about why I have to use GET when for me it makes more sense to use POST.
So, for short I am configuring an API key on the server side and fetching it on the client side so I can use it.
This is the snippet on the server side:
const apiKey = process.env.API_KEY;
console.log(`Your API key is ${apiKey}`);
const dataObject ={};
app.get('/api', (req,res) => {
res.send({key: apiKey})
})
app.get('/all', sendData = (req,res) => {
res.send(dataObject)
})
app.post('/addText', (req,res) => {
let newEntry = {
agreement = req.body.agreement,
subjectivity = req.body.subjectivity
}
dataObject = newEntry;
res.send(dataObject);
} )
And then on the client side I fetch on the '/api' path:
const getApiKey = async () => {
// Getting API key from server
const request = await fetch('/api');
try {
const data = await request.json();
console.log(data);
return data;
}catch(error) {
console.log('ERROR', error);
}
}
Ok, that's working and everything, but my question is:
On the first GET on the server side, I understand that I am sending the API key to the '/api' path so that I can retrieve this key with fetch on the client side. But if I am sending the api key to this path, why am I using GET and not POST?
Sorry if it seems a stupid question but I am having a hard time understanding the GET method.
Thanks!
You are not sending any API key to the server. The server is sending the API key to the client as a response. The client uses a GET request to get the API key from /api. The names of the methods (GET, POST, PUT, DELETE, ...) are from the perspective of the client.
"And then on the client side I fetch on the '/api' path:" No. First the client sends the request with
const request = await fetch('/api');
try {
const data = await request.json();
console.log(data);
return data;
}catch(error) {
console.log('ERROR', error);
}
This triggers the callback in
app.get('/api', (req,res) => {
res.send({key: apiKey})
})
and the server sends the response.
This code returns the API key from the server. It does not send it from the client to the server.
app.get('/api', (req,res) => {
res.send({key: apiKey})
}
The
res.send()
Function is constructing the response returned by the server to the client.
Usually, you use the GET method when the client has to read some data from the server, in this case, you want to read the API_KEY defined in the server. GET has no body, but every request may be parametric by passing parameters in the query string.
On the other hand, when you have to update or create an entity on the server, that's when POST (or PUT) enters into action. Here you pass your data in the body of the request.
I have a websocket that i want to be authenticated with Token Authorization on handshake on opennig. I looked for answers for this problem and almost all of them suggested to store Authorization cookie first with javascript then connect to web socket (so the header will be sent from cookie stored in web page).
But I prefer to not store the token in browser cookie and just send it in my websocket request scope.
Here is a simple javascript code to connect to websocket. I really appreciate it if any one help me on this context:
<script>
const socket = new WebSocket('ws://localhost:8001/announcement');
socket.onopen = function open() {
console.log('WebSockets connection created.');
};
// Listen for messages
socket.addEventListener('announcement', function (event) {
console.log('Message from server ', event.data);
});
</script>
I found a solution. Correct me if I'm wrong.
Right after web socket connection established, send the token to server. And in Server (in my case django channels) in receive method, I fetch that token and if token is valid, I update the connection information, And if the token is not valid disconnect the connection.
something like this:
js file:
const socket = new WebSocket('ws://localhost:8001/announcement');
socket.onopen = function open() {
console.log('WebSockets connection created.');
let authData = {'token': '<valid-token-here>'}
socket.send(JSON.stringify(authData));
};
and on server side (django for example):
def receive(self, text_data=None, bytes_data=None):
if self.scope['user'].id:
pass
else:
try:
# It means user is not authenticated yet.
data = json.loads(text_data)
if 'token' in data.keys():
token = data['token']
user = fetch_user_from_token(token)
self.scope['user'] = user
except Exception as e:
# Data is not valid, so close it.
print(e)
pass
if not self.scope['user'].id:
self.close()
There is examples about how to access FeathersJS API from SSR, but they lack any info on how it is supposed to authorize such requests.
Is it ok to instantiate feathers-client app for every request? Would not it be to heavy?
There is an official example of how to call feathers API from server side:
// Set up a socket connection to our remote API
const socket = io('http://api.feathersjs.com');
const api = client().configure(socketio(socket));
app.get('/messages', function(req, res, next){
api.service('messages')
.find({ query: {$sort: { updatedAt: -1 } } })
.then(result => res.render('message-list', result.data))
.catch(next);
});
But what if the messages service will require authenticated user?
Should i just manually get token from SSR's req and add it somehow to api instance or api.service call?
Taking in mind the asynchronous nature of node it seems that durable way here is to call client() inside the app.get '/messages' handler, is it a supposed way?
It is also unclear does one of the Feathers boilerplate examples have durable SSR authentication, i've described it here.
Here is how i got it working.
On every request SSR create an API adapter before routing:
app.use('/', (req, res, next) => {
req.api = APIClient(req);
next();
});
APIClient constructor gets token from cookie an sets it using the set('accessToken', token) method, provided by feathers-authentication-client plugin:
'use strict';
const feathers = require('feathers');
const superagent = require('superagent');
const hooks = require('feathers-hooks')
const feathers_rest = require('feathers-rest/client');
const auth_plugin = require('feathers-authentication-client');
const config = require('../config');
const host = clientUrl => (
__SERVER__ ? `http://${config.apiHost}:${config.apiPort}` : clientUrl
);
/* API adaptor constructor.
*/
module.exports = function APIClient(req) {
const api = feathers()
// REST plugin gives ability to query services over HTTP,
// superagent used as an isomorphic HTTPClient.
.configure(feathers_rest(host('/api')).superagent(superagent))
.configure(hooks())
// Auth plugin gives ability to set accessToken
.configure(auth_plugin())
;
if (__SERVER__) {
api.set('accessToken', req['cookies']['feathers-jwt']);
}
return api;
}
So, here is a page loading flow i've got:
When one type 'my-app.com' in a browser, it sends a GET request to SSR, passing an access token in feathers-jwt cookie.
SSR creates a feathers client, fetches access token from the cookie and gives it to the client by api.set('accessToken', token) method.
SSR gets data from API using this client and gives it to the template engine (pug/react etc).
SSR returns rendered page to the browser.
Also one need to set token in browser when making requests to API, because if it is on another domain there will be no cookie, and it is better to use Authorization header or token parameter when accessing API.
Discussion link.