I am trying to integrate socket.io for some specific suff.. working fine yet.. but i need information about the user. socket.io credentials are activated, so i can see the payload-token in the cookie. any recommendations to use the payload-token and query my users collection?
const io = new SocketIOServer(server, { cors: { credentials: true } });
io.use((socket, next) => payload.authenticate(socket.request, {}, next));
io.on('connect', ({ request }) => {
console.log(request.user);
});
Unfortunality the request.user is empty. request.islogin and request.isAuthenticated methods for example are available, but request.isAuthenticated() returns false. No idea at the moment, what i am missing here.
request.cookie has payload-token=....
@hendrik01 Did you call payload.authenticate() ?
That should happen after payload.init
And then your requests should include the user
@notchr here:
io.use((socket, next) => payload.authenticate(socket.request, {}, next));
Hmm
Interesting, I haven't seen it used in middleware like that
Usually outside
Hmm, I'm guessing you've tried calling payload.authenticate() prior to your io setup?
(and after payload.init)
@notchr basically i am running:
payload.init(...).then(p => mysocketinitfunction(p)
Can you call payload.init, then call payload.authenticate()
then do you socket init
your
Just to rule out the issue being related to order
Something like this?
Image dissapeared 😮
ops
Are you set on using the .then callback pattern?
(separate question)
no
ok one sec
this is how i setup my server
const app = express();
const router = express.Router();
app.use(express.json());
const start = async () => {
await payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
express: app,
onInit: () => {
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`);
},
});
router.use(payload.authenticate);
app.use("/", router);
app.listen(3000, async () => {
console.log(
"Express is now listening for incoming connections on port 3000."
);
});
};
start();
(Note in my example i have a router because i have custom routes, but you can do the same on app)
@hendrik01 Any luck with that?
@notchr no, but wait i am cleaning up the code
Ok, no rush 🙂
const run = async () => {
await payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
express: app
});
app.use(payload.authenticate);
const io = new SocketIOServer(server, {
cors: {
origin: process.env.FRONTEND_URI,
credentials: true
}
});
io.on('connect', async (socket) => {
console.log('user', socket.request.user);
});
server.listen(3000);
};
run();
but still user: undefined
Hmm
And you confirmed that the user is in fact logged in, right?
yepp
but i think i really need a socketio middleware to handle the authentication
I wonder if you can do like
const wrap = middleware => (socket, next) => middleware(socket.request, {}, next);
io.use(wrap(payload.authenticate));
// only allow authenticated users
io.use((socket, next) => {
const user = socket.request.user;
if (user {
next();
} else {
next(new Error("unauthorized"));
}
});
Not sure if that's the same as your original attempt
it is 🙂
hmm
Can we compare a normal request
Versus the socket request
It seems odd to me that the request wouldn't have the user in both
Since it's a shared request context
Like, add a get request to '/test'
and confirm that request has the user on it
this works
router.get("/test", async (req, res) => {
console.log(req.user)
})
oh that works?
So that's super weird
const run = async () => {
await payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
express: app
});
app.use(payload.authenticate);
app.get('/test', (req, res) => {
return res.send(req.user);
});
const server = http.createServer(app);
const io = new SocketIOServer(server, {
cors: {
origin: process.env.FRONTEND_URI,
credentials: true
}
});
io.use((socket, next) => payload.authenticate(socket.request, {}, next));
io.use((socket, next) => {
const { user } = socket.request;
if (user) next();
else next(new Error('unauthorized'));
});
io.on('connect', async (socket) => {
console.log('user', socket.request.user);
});
server.listen(3000);
};
run();
I wonder if it's related to cookies
maybe related to cors?
maybe make sure the cors config is consistent on socket and app
app.use(
cors({
origin: [
],
credentials: true,
})
);
(with creds)
it is
Hmm
We may need to pull in another community member 😄
hehe
maybe @alessiogr has some ideas 😮
socket.request.headers.cookie has the cookie with the token
Oh heck yea!
I can show you how to validate that
If that's sufficient
it has, was not a question 🙂
Oh you mean you're able to verify the token?
just wanted to say that the token is listed in the socket.request.headers.cookie (because i thought maybe it is missing there and would cause maybe a problem:
https://github.com/payloadcms/payload/blob/master/src/utilities/parseCookies.ts#L5)
So wait, is it solved? I'm confused
You can get the user from the token
Which is similar to getting the user on req.user
@notchr no is not solved. i just see the token, but i am not able to get the user
OK so you can parse the token
how can i get the user with the help of the token?
And get the user
Right, so...
// Token verification
const verifyToken = (
req: express.Request,
res: express.Response
): string | false => {
if (req.cookies["payload-token"]) {
const hash = crypto
.createHash("sha256")
.update(process.env.PAYLOAD_SECRET)
.digest("hex")
.slice(0, 32);
try {
const decoded = jwt.verify(req.cookies["payload-token"], hash, {
algorithms: ["HS256"],
});
if (!decoded) return false;
console.log(decoded) // decoded user object
return decoded
} catch (err) {
console.log("Invalid token request.", err);
return false;
}
} else {
console.log("Missing token in request.");
return false;
}
};
This is how I decode the user token on an external API
Ideally you'd want to just get the user on the request. But until we figure that out, this should also work
The JWT token stores the user information, and whatever additional fields you've specified to be included in the JWT
oh, haven't touched any authentification stuff ever, so far. I currently only need/use api-keys 😅
Ahhh okay 😄
Hmm, maybe @jacobsfletch has some ideas then? 😅
one moment
@notchr bingo!
// Cors
app.use(cors({
origin: [process.env.FRONTEND_URI || 'http://localhost:5173'],
credentials: true,
methods: ['GET', 'PATCH', 'POST']
}));
interface decodedUserToken extends jwt.JwtPayload {
id: string;
email: string;
}
const verifyToken = (token: string): decodedUserToken => {
const hash = crypto
.createHash("sha256")
.update(process.env.PAYLOAD_SECRET)
.digest("hex")
.slice(0, 32);
return jwt.verify(token, hash, {
algorithms: ["HS256"],
}) as decodedUserToken;
};
// Payload
const run = async () => {
await payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
express: app
});
const server = http.createServer(app);
const io = new SocketIOServer(server, {
cors: {
origin: process.env.FRONTEND_URI || 'http://localhost:5173',
credentials: true,
methods: ['GET', 'PATCH', 'POST']
}
});
io.use((socket, next) => {
const cookies = parseCookies(socket.request);
const { id } = verifyToken(cookies['payload-token']);
if (!id) next(new Error('Unauthorized'));
socket.request.socketUserId = id;
next();
});
io.on('connect', async (socket) => {
console.log('user id', socket.request?.socketUserId);
});
server.listen(3000);
};
run();
@notchr 🙂
YOOO
Nicely done!
Sorry, I'm a bit late to the party, but I've had the same issues. After a lot of debugging I figured out, that the request object coming from socket.io (or rather engine.io) is missing two fields that are used by payload.authenticate. Namely
get
and
payload
. So this is my solution now:
async function run(): void {
await payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
express: app
});
const server = http.createServer(app);
const socketServer = new SocketIOServer(server, {
cors: {
origin: [],
credentials: true,
}
});
socketServer.engine.use(prepareRequestForPayloadAuth);
socketServer.engine.use(payload.authenticate);
socketServer.use(ensureSocketAuthenticated);
}
function prepareRequestForPayloadAuth(req: Request, res: Response, next: NextFunction): void {
// We need this stuff for payload JWT authentication to work properly.
// Feel free to refactor this if any of the below referenced code doesn't
// need this anymore or if you find a better way to add these to the request.
// Required for: https://github.com/payloadcms/payload/blob/90720964953d392d85982052b3a4843a5450681e/src/auth/getExtractJWT.ts#L7
// @ts-ignore TBH I have no idea what typescript is complaining about here...
req.get = req.get || ((key: string) => req.headers[key.toLowerCase()]);
// Required for: https://github.com/payloadcms/payload/blob/90720964953d392d85982052b3a4843a5450681e/src/auth/strategies/jwt.ts#L27
req.payload = req.payload || payload;
next();
}
function ensureSocketAuthenticated(socket: ClientSocket, next: (err?: Error) => void): void {
const req = socket.request as Request;
if (req.isUnauthenticated()) {
next(new Error("not authorized"));
} else {
next();
}
}
run();
To be fair I've only tested it with an "Autorization" header, don't know if it also properly works with cookies.
One more noteworthy thing is, that this will only validate the JWT once upon connection. If the token expires and the client doesn't reconnect, then the server won't notice.
Star
Discord
online
Get help straight from the Payload team with an Enterprise License.