WebSocket Authentication
WebSocket tidak punya built-in auth. Kamu harus authenticate saat connection, bukan per-message.
Token-based Auth
// Client: kirim token saat connect
const socket = io("wss://api.example.com", {
auth: {
token: localStorage.getItem("accessToken"),
},
});
// Server: verify di middleware
io.use(async (socket, next) => {
const token = socket.handshake.auth.token;
if (!token) {
return next(new Error("Authentication required"));
}
try {
const user = await verifyToken(token);
socket.data.userId = user.id;
socket.data.user = user;
next();
} catch {
next(new Error("Invalid token"));
}
});
Token Refresh
// Problem: token expires while WebSocket connected
// Solution 1: disconnect + reconnect with new token
socket.on("connect_error", async (err) => {
if (err.message === "Token expired") {
const newToken = await refreshAccessToken();
socket.auth.token = newToken;
socket.connect(); // reconnect with new token
}
});
// Solution 2: server-side refresh
io.use(async (socket, next) => {
const token = socket.handshake.auth.token;
try {
socket.data.user = await verifyToken(token);
next();
} catch (err) {
if (err.name === "TokenExpiredError") {
// Try refresh token
const refresh = socket.handshake.auth.refreshToken;
if (refresh) {
const { user, newToken } = await refreshToken(refresh);
socket.data.user = user;
socket.emit("token:refreshed", { token: newToken });
next();
}
}
next(new Error("Authentication failed"));
}
});
Authorization per Event
// Not every authenticated user can do everything
socket.on("admin:broadcast", (data) => {
if (socket.data.user.role !== "admin") {
socket.emit("error", { message: "Forbidden" });
return;
}
io.emit("announcement", data);
});
socket.on("channel:join", async (channelId) => {
const canJoin = await checkChannelAccess(socket.data.userId, channelId);
if (!canJoin) {
socket.emit("error", { message: "No access to this channel" });
return;
}
socket.join(`ch:${channelId}`);
});