One identity for a page and a protected room
Documentation for Redweb 0.13.3. Install that exact version when following these examples.
A complete local demonstration of shared authentication, decorator-first server HTML and explicitly authorized raw socket room entry.
This pattern demonstrates one API area. Application-specific names, credentials, assets, and policies may need to be supplied. Complete starter recipes include all required application files.
import { randomBytes } from 'node:crypto';
import { page, start, BaseHandler, SocketRoute, RedWebSocket, RedWebRequest, LivePageRequestContext } from 'redweb';
// A runnable local demonstration, not a production credential store.
export function createApp(port = 8181) {
const token = randomBytes(32).toString('base64url');
let enabled = true;
const authenticate = (request: Pick<RedWebRequest, 'headers'>) =>
enabled && request.headers.authorization === `Bearer ${token}` ? 'alice' : false;
@page('/', { authorize: context => context.principal === 'alice' })
class Home {
render({ principal }: LivePageRequestContext) { return <main><h1>Private workspace</h1><p>{principal}</p></main>; }
}
class Join extends BaseHandler {
constructor() { super('join'); }
async onMessage(socket: RedWebSocket) {
socket.sendJson({ joined: await socket.enterRoom!('team'), principal: socket.context!.principal });
}
}
class Team extends SocketRoute {
constructor() {
super({ path: '/team', handlers: [Join], allowDuplicateConnections: true, logger: null,
admission: { authenticate },
rooms: { authorize: (context, roomId) => enabled && context.principal === 'alice' && roomId === 'team' },
});
}
}
const app = start(Home, { listen: false, authenticate, logger: null });
const team = app.sockets!.addRoute(Team);
app.server.listen(port, '127.0.0.1');
return {
app, team, token,
async revoke() {
enabled = false; // Invalidate credentials and future permissions first.
team.clients.forEach(socket => team.rooms!.leaveAll(socket));
await app.revoke('alice');
},
shutdown: () => app.shutdown(),
};
}
if (require.main === module) {
const demo = createApp();
console.log('Local demo: http://127.0.0.1:8181/ and ws://127.0.0.1:8181/team');
console.log(`Authorization: Bearer ${demo.token}`); // One fresh local-demo credential per run.
process.once('SIGTERM', () => void demo.shutdown().catch(console.error));
process.once('SIGINT', () => void demo.shutdown().catch(console.error));
}Notes and boundaries
- Save this as src/app.tsx in an initialized realtime starter, build and start it. The printed token is a fresh local-demo credential; do not publish it or treat this as a production identity service.
- Supply the Authorization header for GET / and the /team WebSocket, then send {"type":"join"}. Browser products should use a secure session/cookie integration; native browser WebSockets cannot set this header.
- Both decorator modes and source-free production execution are checked with real HTTP/WebSockets. The example explicitly revokes raw room memberships as well as Live HTML page sessions.