pluv.io is in preview! Please wait for a v1.0.0 stable release before using this in production.

Authorization

pluv.io uses JWTs to authorize access to rooms on a new connection. To generate a JWT, you will need to setup an authentication endpoint to determine if the user should have access to the room.

The examples below will use express, but so long as you can create an http endpoint that can return text responses, these should still be relevant.

Enable authorization on io

Set the authorize property on your createIO config to enable authorization on your io instance.

// server/io.ts

import { createIO } from "@pluv/io";
import { platformNode } from "@pluv/platform-node";
import { z } from "zod";

export const io = createIO({
    authorize: {
        // If required is false, users can authenticate for a room to
        // attach an identity to their presence. Otherwise they will be
        // anonymous.
        required: true,
        // The secret for generating your JWT
        secret: process.env.PLUV_AUTH_SECRET!,
        // The shape of your user object. `id` must always be required.
        user: z.object({
            id: z.string(),
            // Here is an additional field we wish to add.
            name: z.string(),
        }),
    },
    platform: platformNode(),
});

export const ioServer = io.server();

export type AppPluvIO = typeof ioServer;

Setup an authorization endpoint

To add custom authorization, you'll need to define an http endpoint to return a JWT from.

// server/server.ts

import express from "express";
import Http from "http";
import { io } from "./io";

const PORT = 3000;

const app = express();
const server = Http.createServer();

app.get("/api/authorize", async (req, res) => {
    const room = req.query.room as string;

    // ... Implement your custom authorization here

    const token = await io.createToken({
        req,
        room,
        user: {
            id: "abc123",
            name: "leedavidcs",
        },
    });

    return res.send(token).status(200);
});

server.listen(PORT, () => {
    console.log(`Server is listening on port: ${port}`);
});

Connect to your authorization endpoint

Now that we have your endpoint defined, connect your frontend client to your authorization endpoint.

// frontend/io.ts

import { createClient } from "@pluv/react";
import type { AppPluvIO } from "server/io";

const client = createClient<AppPluvIO>({
    // Specify your auth endpoint here
    // Set to `true` if you have authorization on your io instance, and you
    // want to use the default of (room) => `/api/pluv/authorize?room=${room}`
    // This is the default from `createPluvHandler`
    authEndpoint: ({ room }) => `/api/authorize?room=${room}`,
    // ...
});

How to use POST requests

// frontend/io.ts

import { createClient } from "@pluv/react";
import type { AppPluvIO } from "server/io";

const client = createClient<AppPluvIO>({
    authEndpoint: ({ room }) => ({
        url: "/api/authorize",
        // You can use fetch options as well like so
        options: {
            method: "POST",
            body: JSON.stringify({ room }),
        },
    }),
    // ...
});

Add token to room registration

When the frontend receives a JWT from your authorization endpoint, it will add that token as a token query parameter to your websocket connection request. To use this token, pass the token into io.room.register on your server.

import type { InferIORoom } from "@pluv/io";
import express from "express";
import Http from "http";
import WebSocket from "ws";
import { io, ioServer } from "./io";

const PORT = 3000;

const app = express();
const server = Http.createServer();
const wsServer = new WebSocket.Server({ server });

const parseRoomId = (url: string): string => {
    /* get room from req.url */
};

const parseToken = (url: string): string => {
    /* get token from query parameters of req.url */
};

const rooms = new Map<string, InferIORoom<typeof ioServer>>();
const getRoom = (roomId: string): InferIORoom<typeof ioServer> => {
    const existing = rooms.get(roomId);

    if (existing) return existing;

    const room = ioServer.createRoom(roomId, {
        onDelete: (event) => {
            rooms.delete(event.room);
        },
    });
    rooms.set(roomId, room);

    return room;
};

wsServer.on("connection", async (ws, req) => {
    const roomId = parseRoomId(req.url);
    const token = parseToken(req.url);
    const room = getRoom(roomId);

    // Pass in the token from query params
    await room.register(ws, { token });
});

app.get("/api/authorize", async (req, res) => {
    const room = req.query.room as string;

    // ... Implement your custom authorization here

    const token = await io.createToken({
        req,
        room,
        user: {
            id: "abc123",
            name: "leedavidcs",
        },
    });

    return res.send(token).status(200);
});

server.listen(PORT, () => {
    console.log(`Server is listening on port: ${port}`);
});