Authentication & Authorization¶
This page shows a complete auth flow using WebMQ hooks on both server and client, combined with a React context hook for clean component usage.
The pattern: login endpoint issues a JWT → client stores it → client identify hook attaches it → server identify hook verifies it → server listen/publish hooks enforce role-based rules.
Server¶
Login endpoint validates credentials and returns a signed JWT with role:
import express from 'express';
import jwt from 'jsonwebtoken';
const SECRET = 'your-secret-key';
const app = express();
app.post('/login', express.json(), (req, res) => {
const { username, password } = req.body;
// Replace with real credential check
if (password !== `${username}${username}`) {
return res.status(403).json({ error: 'Invalid credentials' });
}
const role = username === 'admin' ? 'admin' : 'user';
const token = jwt.sign({ username, role }, SECRET, { expiresIn: '1h' });
res.json({ token, role });
});
WebMQ server hooks verify every identify, then authorize listen/publish by role:
import WebMQServer from 'webmq-backend';
const server = new WebMQServer({
rmqUrl: 'amqp://localhost',
exchange: 'auth_app',
port: 8080
});
// Verify JWT on identify, store user in context
server.addHook('identify', async (header, context) => {
const { token } = header;
if (!token) throw new Error('Token required');
const decoded = jwt.verify(token, SECRET);
context.user = decoded; // { username, role }
return header;
});
// Block any action if not identified
server.addHook('pre', async (header, context) => {
if (!context.user) throw new Error('Not authenticated');
return header;
});
// Authorize listen by role
server.addHook('listen', async (header, context) => {
const { role, username } = context.user;
const { bindingKey } = header;
if (role === 'admin') return header; // can listen anywhere
// Regular users can only hear their own topic
if (bindingKey !== `user.${username}`) {
throw new Error(`Cannot listen to ${bindingKey}`);
}
return header;
});
// Authorize publish by role
server.addHook('publish', async (header, context) => {
const { role, username } = context.user;
const { routingKey } = header;
if (role === 'admin') return header; // can publish anywhere
if (routingKey !== `user.${username}`) {
throw new Error(`Cannot publish to ${routingKey}`);
}
return header;
});
await server.start();
Client (React)¶
AuthProvider manages auth state and the WebMQ client lifecycle:
import { createContext, useContext, useState, useEffect } from 'react';
import WebMQClient from 'webmq-frontend';
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [token, setToken] = useState(null);
const [role, setRole] = useState(null);
const [wmqClient, setWmqClient] = useState(null);
// WebMQ client follows token: connect on login, disconnect on logout
useEffect(() => {
if (!token) {
wmqClient?.disconnect();
setWmqClient(null);
return;
}
const wmq = new WebMQClient({
url: 'ws://localhost:8080',
sessionId: crypto.randomUUID(),
});
wmq.addHook('identify', (h) => {
h.token = token;
return h;
});
wmq.connect();
setWmqClient(wmq);
return () => wmq.disconnect();
}, [token]);
const login = async (username, password) => {
const res = await fetch('/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
if (!res.ok) throw new Error('Login failed');
const { token: t, role: r } = await res.json();
setToken(t);
setRole(r);
};
const logout = () => {
setToken(null);
setRole(null);
};
return (
<AuthContext.Provider value={{ token, role, wmqClient, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
return ctx;
}
Role-based UI gates content by auth state:
function App() {
const { token, role, login, logout } = useAuth();
if (!token) return <LoginForm onLogin={login} />;
return (
<div>
<header>
Logged in as {role}
<button onClick={logout}>Logout</button>
</header>
{role === 'admin' ? <AdminPanel /> : <UserDashboard />}
</div>
);
}
function LoginForm({ onLogin }) {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try { await onLogin(username, password); }
catch { alert('Login failed'); }
};
return (
<form onSubmit={handleSubmit}>
<input value={username} onChange={(e) => setUsername(e.target.value)} placeholder="Username" />
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" />
<button>Log in</button>
</form>
);
}
Admin and user components use the client through useAuth:
function AdminPanel() {
const { wmqClient } = useAuth();
// wmqClient.listen('admin.events', ...)
// wmqClient.publish('admin.commands', ...)
}
function UserDashboard() {
const { wmqClient } = useAuth();
// wmqClient.listen('user.alice', ...)
// wmqClient.publish('user.alice', ...)
}
Key points¶
- Token sent once during identify, not on every message
- Server
contextpersists per-connection — user info available to all subsequent hooks prehook acts as a gate for all actions after identifylisten/publishhooks enforce role-based authorization at the messaging layer- React context keeps auth state and WebMQ client in sync — no manual disconnect/reconnect