How-to Guides
Authentication (JWT)
API keys, member JWTs, roles, token refresh, and WebSocket auth.
Authentication
FluxyChat uses two credential forms:
- API key (
X-Fluxy-Api-Key): server-to-server, identifies the tenant/project. - JWT (
Authorization: Bearer …): client-to-worker/SDK. Claims includesub(userId),tid(projectId),roles,exp.
Rule: fc_ stays on the server. Browsers may use pk_ (publishable) for guest-session and anonymous tokens only. See Publishable keys.
Mint a member JWT (server-side)
export FLUXY_BASE_URL="http://127.0.0.1:8787"
export FLUXY_API_KEY="fc_..."
curl -sS -X POST "$FLUXY_BASE_URL/auth/token" \
-H "Content-Type: application/json" \
-H "X-Fluxy-Api-Key: $FLUXY_API_KEY" \
-d '{"userId":"alice","roles":["member"],"ttlSeconds":3600}'Response includes token, expiresIn, and echoed claims.
Roles
| Role | Typical access |
|---|---|
member | Send/edit own messages, reactions, read receipts, inbox |
moderator | Mute/ban, some admin routes |
admin | Webhooks, projects, alert rules |
owner | Sensitive tenant operations |
Admin routes enforce role checks per endpoint. See OpenAPI docs for each route.
Next.js Route Handler example
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const body = await request.json().catch(() => null);
const userId = body?.userId;
if (!userId) return NextResponse.json({ error: "userId required" }, { status: 400 });
const res = await fetch(`${process.env.FLUXY_BASE_URL}/auth/token`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Fluxy-Api-Key": process.env.FLUXY_API_KEY!,
},
body: JSON.stringify({ userId, roles: ["member"], ttlSeconds: 3600 }),
});
return NextResponse.json(await res.json(), { status: res.status });
}Wire the returned JWT into FluxyRealtimeProvider or FluxyChatClient({ token }).
WebSocket auth
GET /ws/room/:roomId?token=<JWT>Membership is enforced: 403 / close 1008 when the user is not a room member.
Token refresh
FluxyChatClient.setToken(next) (alias updateToken) replaces the session JWT. Returns true when sub changed. Reconnect active rooms.
When exp passes:
- Mint a new JWT on your backend (
POST /auth/token). - Call
client.setToken(jwt), or pass a function toFluxyRealtimeProviderauthTokenProviderso it remints before expiry:
<FluxyRealtimeProvider
workerUrl={process.env.NEXT_PUBLIC_FLUXYCHAT_WORKER_URL!}
authTokenProvider={async () => {
const res = await fetch("/api/fluxy/token", { method: "POST" });
const data = (await res.json()) as { token: string };
return data.token;
}}
>
{children}
</FluxyRealtimeProvider>Keep FLUXY_API_KEY (fc_) in the Route Handler only. Public rooms skip this path: publishableKey or joinPublicRoomAsGuest.
Common errors
| Symptom | Cause | Fix |
|---|---|---|
| 401 invalid api key | Revoked or wrong project key | Rotate key in Projects console |
FluxyTokenExpiredError | JWT exp passed | Mint new JWT |
FluxyNotMemberError | User not in room | Add membership or join room |
FluxyAnonymousNotAllowedError | Room requires auth | Use member JWT |
See Troubleshooting integration for copy-paste fixes.