Socket.IO Authentication - authentication

I am trying to use Socket.IO in Node.js, and am trying to allow the server to give an identity to each of the Socket.IO clients. As the socket code is outside the scope of the http server code, it doesn't have easy access to the request information sent, so I'm assuming it will need to be sent up during the connection. What is the best way to
1) get the information to the server about who is connecting via Socket.IO
2) authenticate who they say they are (I'm currently using Express, if that makes things any easier)

Use connect-redis and have redis as your session store for all authenticated users. Make sure on authentication you send the key (normally req.sessionID) to the client. Have the client store this key in a cookie.
On socket connect (or anytime later) fetch this key from the cookie and send it back to the server. Fetch the session information in redis using this key. (GET key)
Eg:
Server side (with redis as session store):
req.session.regenerate...
res.send({rediskey: req.sessionID});
Client side:
//store the key in a cookie
SetCookie('rediskey', <%= rediskey %>); //http://msdn.microsoft.com/en-us/library/ms533693(v=vs.85).aspx
//then when socket is connected, fetch the rediskey from the document.cookie and send it back to server
var socket = new io.Socket();
socket.on('connect', function() {
var rediskey = GetCookie('rediskey'); //http://msdn.microsoft.com/en-us/library/ms533693(v=vs.85).aspx
socket.send({rediskey: rediskey});
});
Server side:
//in io.on('connection')
io.on('connection', function(client) {
client.on('message', function(message) {
if(message.rediskey) {
//fetch session info from redis
redisclient.get(message.rediskey, function(e, c) {
client.user_logged_in = c.username;
});
}
});
});

I also liked the way pusherapp does private channels.
A unique socket id is generated and
sent to the browser by Pusher. This is
sent to your application (1) via an
AJAX request which authorizes the user
to access the channel against your
existing authentication system. If
successful your application returns an
authorization string to the browser
signed with you Pusher secret. This is
sent to Pusher over the WebSocket,
which completes the authorization (2)
if the authorization string matches.
Because also socket.io has unique socket_id for every socket.
socket.on('connect', function() {
console.log(socket.transport.sessionid);
});
They used signed authorization strings to authorize users.
I haven't yet mirrored this to socket.io, but I think it could be pretty interesting concept.

I know this is bit old, but for future readers in addition to the approach of parsing cookie and retrieving the session from the storage (eg. passport.socketio ) you might also consider a token based approach.
In this example I use JSON Web Tokens which are pretty standard. You have to give to the client page the token, in this example imagine an authentication endpoint that returns JWT:
var jwt = require('jsonwebtoken');
// other requires
app.post('/login', function (req, res) {
// TODO: validate the actual user user
var profile = {
first_name: 'John',
last_name: 'Doe',
email: 'john#doe.com',
id: 123
};
// we are sending the profile in the token
var token = jwt.sign(profile, jwtSecret, { expiresInMinutes: 60*5 });
res.json({token: token});
});
Now, your socket.io server can be configured as follows:
var socketioJwt = require('socketio-jwt');
var sio = socketIo.listen(server);
sio.set('authorization', socketioJwt.authorize({
secret: jwtSecret,
handshake: true
}));
sio.sockets
.on('connection', function (socket) {
console.log(socket.handshake.decoded_token.email, 'has joined');
//socket.on('event');
});
The socket.io-jwt middleware expects the token in a query string, so from the client you only have to attach it when connecting:
var socket = io.connect('', {
query: 'token=' + token
});
I wrote a more detailed explanation about this method and cookies here.

Here is my attempt to have the following working:
express: 4.14
socket.io: 1.5
passport (using sessions): 0.3
redis: 2.6 (Really fast data structure to handle sessions; but you can use others like MongoDB too. However, I encourage you to use this for session data + MongoDB to store other persistent data like Users)
Since you might want to add some API requests as well, we'll also use http package to have both HTTP and Web socket working in the same port.
server.js
The following extract only includes everything you need to set the previous technologies up. You can see the complete server.js version which I used in one of my projects here.
import http from 'http';
import express from 'express';
import passport from 'passport';
import { createClient as createRedisClient } from 'redis';
import connectRedis from 'connect-redis';
import Socketio from 'socket.io';
// Your own socket handler file, it's optional. Explained below.
import socketConnectionHandler from './sockets';
// Configuration about your Redis session data structure.
const redisClient = createRedisClient();
const RedisStore = connectRedis(Session);
const dbSession = new RedisStore({
client: redisClient,
host: 'localhost',
port: 27017,
prefix: 'stackoverflow_',
disableTTL: true
});
// Let's configure Express to use our Redis storage to handle
// sessions as well. You'll probably want Express to handle your
// sessions as well and share the same storage as your socket.io
// does (i.e. for handling AJAX logins).
const session = Session({
resave: true,
saveUninitialized: true,
key: 'SID', // this will be used for the session cookie identifier
secret: 'secret key',
store: dbSession
});
app.use(session);
// Let's initialize passport by using their middlewares, which do
//everything pretty much automatically. (you have to configure login
// / register strategies on your own though (see reference 1)
app.use(passport.initialize());
app.use(passport.session());
// Socket.IO
const io = Socketio(server);
io.use((socket, next) => {
session(socket.handshake, {}, next);
});
io.on('connection', socketConnectionHandler);
// socket.io is ready; remember that ^this^ variable is just the
// name that we gave to our own socket.io handler file (explained
// just after this).
// Start server. This will start both socket.io and our optional
// AJAX API in the given port.
const port = 3000; // Move this onto an environment variable,
// it'll look more professional.
server.listen(port);
console.info(`🌐 API listening on port ${port}`);
console.info(`🗲 Socket listening on port ${port}`);
sockets/index.js
Our socketConnectionHandler, I just don't like putting everything inside server.js (even though you perfectly could), especially since this file can end up containing quite a lot of code pretty quickly.
export default function connectionHandler(socket) {
const userId = socket.handshake.session.passport &&
socket.handshake.session.passport.user;
// If the user is not logged in, you might find ^this^
// socket.handshake.session.passport variable undefined.
// Give the user a warm welcome.
console.info(`⚡︎ New connection: ${userId}`);
socket.emit('Grettings', `Grettings ${userId}`);
// Handle disconnection.
socket.on('disconnect', () => {
if (process.env.NODE_ENV !== 'production') {
console.info(`⚡︎ Disconnection: ${userId}`);
}
});
}
Extra material (client):
Just a very basic version of what the JavaScript socket.io client could be:
import io from 'socket.io-client';
const socketPath = '/socket.io'; // <- Default path.
// But you could configure your server
// to something like /api/socket.io
const socket = io.connect('localhost:3000', { path: socketPath });
socket.on('connect', () => {
console.info('Connected');
socket.on('Grettings', (data) => {
console.info(`Server gretting: ${data}`);
});
});
socket.on('connect_error', (error) => {
console.error(`Connection error: ${error}`);
});
References:
I just couldn't reference inside the code, so I moved it here.
1: How to set up your Passport strategies: https://scotch.io/tutorials/easy-node-authentication-setup-and-local#handling-signupregistration

This article (http://simplapi.wordpress.com/2012/04/13/php-and-node-js-session-share-redi/) shows how to
store sessions of the HTTP server in Redis (using Predis)
get these sessions from Redis in node.js by the session id sent in a cookie
Using this code you are able to get them in socket.io, too.
var io = require('socket.io').listen(8081);
var cookie = require('cookie');
var redis = require('redis'), client = redis.createClient();
io.sockets.on('connection', function (socket) {
var cookies = cookie.parse(socket.handshake.headers['cookie']);
console.log(cookies.PHPSESSID);
client.get('sessions/' + cookies.PHPSESSID, function(err, reply) {
console.log(JSON.parse(reply));
});
});

use session and Redis between c/s
Server side
io.use(function(socket, next) {
// get here session id
console.log(socket.handshake.headers.cookie); and match from redis session data
next();
});

this should do it
//server side
io.sockets.on('connection', function (con) {
console.log(con.id)
})
//client side
var io = io.connect('http://...')
console.log(io.sessionid)

Related

#shopify/shopify-api nodejs - Set permanent access token for private app installed on only one store

The shopifyApi reference does not have a private app permanent access token property. I have a custom private app that is installed on only one store and I have the permanent access token so I don't need to oAuth every time I'm calling the REST API. Would be great to have a documented straight forward example of how to do this. The docs are hairy, IMO.
#shopify/shopify-api version: 6.1.0
Is the following close? I'm looking at the Session object and trying to understand how to load an offline session and set the Session with correct SessionParams.
import '#shopify/shopify-api/adapters/node'
import { shopifyApi, LATEST_API_VERSION, Session } from '#shopify/shopify-api'
const shopify = shopifyApi({
apiKey: 'myprivateappkey',
apiSecretKey: 'myprivateappsecret',
apiVersion: LATEST_API_VERSION,
isPrivateApp: true,
scopes: ['read_products'],
isEmbeddedApp: false,
hostName: 'shop.myshopify.com',
})
const sessionId = shopify.session.getOfflineId('shop.myshopify.com')
const session = new Session({
id: sessionId,
shop: 'shop.myshopify.com',
state: 'state',
isOnline: false,
accessToken: 'permanentAccessToken',
})
const client = new shopify.clients.Rest({ session: session })
On older v5 version of shopify-api, with two lines I could access any REST API, but I see this is now deprecated, So I'm trying to unravel offline sessions, but it doesn't make sense to provide the api key and api secret and then create a session using the permanent access token.
import Shopify from '#shopify/shopify-api'
const client = new Shopify.Clients.Rest(
'mystore.myshopify.com',
'permanentAccessTokenString',
)
See answer from Shopify here.
When creating the client in a private app, a dummy session can be created like so
const session = new Session({
id: 'not-a-real-session-id',
shop: 'shop.myshopify.com,
state: 'state',
isOnline: false,
});
const client = new shopify.clients.Rest({ session: session })
When config.isPrivateApp is set to true only the shop property is used by the client - the other three (id, state, isOnline) properties are ignored (but are required when creating a Session object).
config.apiSecretKey is used as the access token, and is read directly from the config (no need to set the accessToken property of the dummy session as it will be ignored).
Essentially, config.apiSecretKey is the permanent access token for a private app.
The scopes can be omitted, as it defaults to an empty scopes array internally anyway and (from a quick search through the library code) is only used when doing OAuth, validating sessions, etc., which won't apply to private apps.
As for the apiKey, while it's mostly used as part of the OAuth process, it is also used in a few other places (e.g., shopify.auth.getEmbeddedAppUrl()), so I'd recommend setting the apiKey to be that of your private app.
However, in my testing, scopes, even when isPrivateApp, are currently required. If you leave the array empty for scopes it will have a config error.
Also, my shopifyApi config mounts rest resources so that when you're in Shopify REST docs on the nodejs tab, you can easily use the REST resources examples on the nodejs to make calls, rather than creating a REST client and use standard post, etc.
Complete code to setup a private app connection to Shopify:
import '#shopify/shopify-api/adapters/node'
import { shopifyApi, LATEST_API_VERSION, Session } from '#shopify/shopify-api'
import { restResources } from '#shopify/shopify-api/rest/admin/2023-01'
const debug = process.env.FUNCTIONS_EMULATOR === 'true'
const shopify = shopifyApi({
apiKey: 'myprivateAppApiKey',
apiSecretKey: 'myPermanentAccessToken',
apiVersion: LATEST_API_VERSION,
isPrivateApp: true,
scopes: [
'read_customers',
'write_customers',
'read_fulfillments',
'write_fulfillments',
'read_inventory',
'write_inventory',
'write_order_edits',
'read_order_edits',
'write_orders',
'read_orders',
'write_products',
'read_products',
],
isEmbeddedApp: false,
hostName: debug ? '127.0.0.1:5001' : 'shop.myshopify.com',
// Mount REST resources.
restResources,
})
// Create a sanitized "fake" sessionId. E.g.
// "offline_my.myshopify.com".
const sessionId = shopify.session.getOfflineId('shop.myshopify.com')
const session = new Session({
id: sessionId,
shop: 'shop.myshopify.com,
state: 'state',
isOnline: false,
})
// Use mounted REST resources to make calls.
const transactions = await shopify.rest.Transaction.all({
session,
order_id: 123456789,
})
// Alternatively, if not using mounted REST resources
// you could create a standard REST client.
const client = new shopify.clients.Rest({ session })

keycloak-connect / express middleware failes to validate token

My problem: i have a client where users successfully authenticate against keycloak (using keycloak-js) in a "frontend"-client. I then use the "keycloak.token" and forward this to a node/express/keycloak-connect backend. There, the validation of the token seems to fail, i.e. I receive http-403 forbidden. Yet, I do not really understand why the validation fails. Both frontend and backend are member of the same realm, given in the JWT, timings fit, aud/iss/sub also match. Roles are there. Etc.. Unfortunately, I could not find any "debug"-hook in the keycloak middleware to trace (and understand) what happens. Did you every came across? Have I misconfigured the keycloak?
Here is my server code:
keycloak.ts
// #ts-ignore
import Keycloak from "keycloak-connect"
// #ts-ignore
import session from "express-session"
let _keycloak: Keycloak.Keycloak | undefined = undefined;
const memoryStore = new session.MemoryStore();
export function getKeycloak(): Keycloak.Keycloak {
if (!_keycloak) {
_keycloak = new Keycloak({ store: memoryStore }, {
"realm": "ear",
"bearer-only": true,
"auth-server-url": "http://192.168.76.4:40002/auth/",
"ssl-required": "external",
"resource": "ear-backend",
"confidential-port": 0
}); }
return _keycloak;
}
Here is my "main" app - server.ts
import {getKeycloak} from "./keycloak";
import express from "express";
import {Request,Response} from "express";
// #ts-ignore
import cors from "cors";
var app = express();
app.use(cors());
var router = express.Router();
router.get('/user', getKeycloak().protect(), function(req, res){ res.send("Hello User");});
app.use("/test", router);
app.listen(3001);
Here is the bearer token I forward from the client to the server:
authorization: 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkaDEweTB2WWhnUFVmcWppQVZ3ZVBXUUxTYmpxUWVkaWprZHFBMUVLbUdJIn0.eyJleHAiOjE2NzQzODM5NjgsImlhdCI6MTY3NDM4MzY2OCwiYXV0aF90aW1lIjoxNjc0MzgzNjY4LCJqdGkiOiJmYjhjMTJ kMy0xMmFmLTQ1M2MtYmE1Mi0yNzA3ZDkwYTNkMDEiLCJpc3MiOiJodHRwOi8vMTkyLjE2OC43Ni40OjQwMDAyL2F1dGgvcmVhbG1zL2VhciIsImF1ZCI6ImVhci1iYWNrZW5kIiwic3ViIjoiMDQxNTY1ZDQtYzM0Ni00MDRmLWFmYzYtMjVjMzRmMGViNWM5IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZWFyLWZ yb250ZW5kIiwibm9uY2UiOiI2ZGUzOGYzMy00MmQyLTQ4ZTItODQwMi01M2I4OTViNzc4Y2YiLCJzZXNzaW9uX3N0YXRlIjoiYTEyYzhkY2ItMjk5Yy00Y2VlLWEyYWQtOThkN2M1MDJkMDgzIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyIqIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJlYXI tcm9sZTEiXX0sInJlc291cmNlX2FjY2VzcyI6eyJlYXItYmFja2VuZCI6eyJyb2xlcyI6WyJmZWF0dXJlMiIsImZlYXR1cmUxIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSIsInNpZCI6ImExMmM4ZGNiLTI5OWMtNGNlZS1hMmFkLTk4ZDdjNTAyZDA4MyIsImVtYWlsX3ZlcmlmaWVkIjp0cnV lLCJuYW1lIjoiVXNlciAxMiIsInByZWZlcnJlZF91c2VybmFtZSI6InVzZXIxMiIsImdpdmVuX25hbWUiOiJVc2VyIiwiZmFtaWx5X25hbWUiOiIxMiIsImVtYWlsIjoidXNlci4xMkBhY21lLmNvbSJ9.U8YRbUuOdSPKfDeUIfAb5r12ZGhaTB-GCspY9RjKW8iMn2btbTDd9v9TQAvFbvgHHQ7F0QOtwZvfgW DludO1H3pUiEuhzkRcrw7NQLN4WY3atUrIEACpMa5pthZXzDzia3VBYlChMZPnsMOCAq5-fhULhjYz-4SY6YrTpL5TcDcCuAj28-CZt00hqxGQvd2q-LCpRuFXQ6GnxV0fYJZVtX_yolTAJAVHph7uG_WxgBqpxqdh9QBQ1av3Jn9yElo9qZjzbaD261WR1sae6idkSdLmZai1c7r9zVl9MDw8_qE8tzuaY20gIOG2HpUS4sqW7oM9I5cwMBKCHOzODpXkZg'
And, finally, here is the keycloak config export (very lengthy, unfortunately, but I do not know which parts can be safely deleted). It can be downloaded from my Google Drive, since the file would exceed the maxmimum post length: https://drive.google.com/file/d/1vlZC5-W3AOElsTU12p7NldFmFZSGABkA/view?usp=share_link
Thank you for any hints.

Run custom functions in express-gateway

I have this configuration in the gateway.config.yml (Express-Gateway api):
- bo
policies:
- jwt:
- action:
secretOrPublicKeyFile: './key.pem'
checkCredentialExistence: false
Everything works fine, but I want the client to encode/encrypt a token that it is being sent to make sure even if I have the token storage on the localstorage no one can use it because it will need to be signed by the client.
The only problem with this is, how can I run a code to decode/decrypt the token before Express-Gateway jwt policy try to validate the token?
Because express-gateway can use middlewares like any other express application I think this is possible, but not an idea on how to do it.
I created this policy that will help me, but how can I integrate it with the express-gateway api:
const cryptojs = require("crypto-js");
module.exports = {
name: 'decode',
policy: (actionParams) => {
return (req, res, next) => {
const tokenHeader = req.header('Authorization');
const tokenArray = tokenHeader.split(' ');
const tokenCifer = tokenArray[1];
const bytes = cryptojs.AES.decrypt(tokenCifer, 'superkeyperm'); //CryptoJS.AES.decrypt(ciphertext.toString(), 'secret key 123');
var token = bytes.toString(cryptojs.enc.Utf8);
req.headers.authorization = `Bearer ${token}`;
next() // calling next policy
};
}
};
I think what you're interested is writing a plugin which is nothing more than a collection of additional middleware and condition you can stack in Express Gateway, where you can put your own logic.
Check out the docs at https://www.express-gateway.io/docs/plugins/

handle user after Oauth2 redirect

I create an application that client (vue) runs on localhost:3000 and server (express) on localhost: 8080.
I use passport-google-oauth20 strategy to log in using google and only JWT token.
My Question is:
How do I redirect to a client in a callback strategy so that the receives information about the logged-in user on client side? I'm using in passport jwt, which i send on local-login strategy and there everything works.
At the moment, it looks like this to me:
this.router.get('/v1/auth/google',
passport.authenticate('google', { session: false, scope: ['email', 'profile']})
);
this.router.get('/v1/auth/google/callback',
passport.authenticate('google', { session: false }), (req, res) => {
// let token = sign(req.user);
res.redirect(`http://localhost:3000`);
// how to redirect to the CLIENT,
// who will receive data about the logged-in user?
// how get user data, catch this situation?
});

Calling express-session backed by connect-redis inside websockets/ws

Okay so here goes. I am trying to to use express-session backed by connect-redis together with websockets/ws. The vast majority of example code I have seen usually includes the use of Express to serve the client a websocket script of some type and then configuring the server app to use the express-session middleware to work with session data...with websockets further down the process chain.
My client side app on the other hand immediately initiates an Opening Handshake/Upgrade request, which bypasses Express and heads straight into websockets/ws. For this reason I am not able to work with session data through the commonly used app.use(session(...) middleware setup.
The below code uses a setup that allows me to call express-session inside websocket/ws and to put the connection request through express-session so I can get at the session data. This works as is shown by the output also below. What does not work however is backing express-session by connect-redis inside this setup. To check I call a redis client directly to query my redis box after express-session has run, no keys are returned.
I have three questions:
1. Is what I am doing below the 'correct' way of integrating express-session with websockets/ws for the client setup described?
2. If not what should I be doing?
3. If this is a good way, is it possible to get connect-redis working as session store?
Your insights are very welcome.
Server-side code:
const express = require('express');
const Session = require('express-session');
const http = require('http');
const ws = require('ws');
const util = require('util');
const redis = require('redis');
const client = redis.createClient(6379, '10.0.130.10', {no_ready_check: true});
const RedisStore = require('connect-redis')(Session);
const SessionStore = new RedisStore({host: '10.0.130.10', port: '6379', ttl: 60, logErrors: true});
//const SessionStore = new Session.MemoryStore();
var session = Session({
store: SessionStore,
cookie: {secure: true, maxAge: 3600, httpOnly: true},
resave: false,
saveUninitialized: true,
secret: '12345'
});
// Define Express and WS servers
const app = express();
const server = http.createServer(app);
const wss = new ws.Server({ server });
// WS Websocket
wss.on('connection', function connection(ws, req) {
session(req, {}, function(){
let sessionId = req.sessionID;
console.log('SessionID: ' + sessionId);
let sessionCookie = req.session.cookie;
console.log('SessionCookie: ' + JSON.stringify(sessionCookie));
});
client.keys('*', function(err, reply) {
// reply is null when the key is missing
console.log('Redis reponse: ' + reply);
});
});
server.listen(10031, function listening() {
console.log('Listening on: ' + server.address().port);
});
Server-side console.log output (note the Redis response):
Listening on: 10031
SessionID: Oi8AdsoZTAm3hRLmxPfGo43Kmmj_Yd6F
SessionCookie: {"originalMaxAge":3599,"expires":"2017-05-29T17:45:54.467Z","secure":true,"httpOnly":true,"path":"/"}
Redis reponse:
Reference:
express-session, connect-redis and einaros/ws
Reference:
ExpressJS & Websocket & session sharing