Switching Organizations
How users switch between organizations and how the active organization is reflected in JWT tokens.
Switching Organizations
Users who belong to multiple organizations can switch between them. The active organization determines the JWT claims (org_id, role) sent in API requests.
How Organization Switching Works
Switching organizations updates the JWT claims. HelloJohn issues a new access token scoped to the selected organization.
User selects Org B
→ SDK requests new token for Org B
→ New access token contains org_id: "org_B", role: "admin"
→ All subsequent requests use this tokenSwitching via SDK
import { useHelloJohn } from "@hellojohn/react";
function OrgSwitcher() {
const { organizations, activeOrganization, setActiveOrganization } =
useHelloJohn();
return (
<select
value={activeOrganization?.id}
onChange={(e) => setActiveOrganization(e.target.value)}
>
{organizations.map((org) => (
<option key={org.id} value={org.id}>
{org.name}
</option>
))}
</select>
);
}setActiveOrganization handles token refresh automatically.
Reading the Active Organization
import { useOrganization } from "@hellojohn/react";
function Dashboard() {
const { organization, isLoaded } = useOrganization();
if (!isLoaded) return <p>Loading...</p>;
if (!organization) {
return <p>No active organization. Please select one.</p>;
}
return (
<div>
<h1>{organization.name}</h1>
<p>Your role: {organization.membership.role}</p>
</div>
);
}JWT Claims After Switching
Before switching:
{
"sub": "usr_01HABCDEF123456",
"org_id": null,
"role": null
}After switching to an organization where the user is an admin:
{
"sub": "usr_01HABCDEF123456",
"org_id": "org_01HABCDEF777666",
"role": "admin"
}Backend: Extracting Active Organization
// Middleware
function extractOrg(req: Request, res: Response, next: NextFunction) {
const { org_id, role } = req.token;
if (!org_id) {
req.org = null;
return next();
}
req.org = { id: org_id, role };
next();
}
// Route that requires an active org
app.get("/org/data", extractOrg, (req, res) => {
if (!req.org) {
return res.status(400).json({ error: "No active organization selected" });
}
// Scoped to req.org.id
const data = await getOrgData(req.org.id);
res.json(data);
});Persisting the Active Organization
The SDK stores the active organization selection in memory. On page reload, you can restore it:
// On app load, restore last-used org
const lastOrgId = localStorage.getItem("lastOrgId");
if (lastOrgId && organizations.find((o) => o.id === lastOrgId)) {
await setActiveOrganization(lastOrgId);
}
// On switch, persist the choice
const handleSwitch = async (orgId: string) => {
await setActiveOrganization(orgId);
localStorage.setItem("lastOrgId", orgId);
};Don't store sensitive data in
localStorage. The org ID is not sensitive.
No Active Organization
Some apps allow users to operate without an active organization (e.g., personal dashboard). In this state, org_id and role are null in the JWT.
// Deselect the active organization
await hj.setActiveOrganization(null);Building a Full Org Switcher
import { useHelloJohn, useOrganization } from "@hellojohn/react";
function OrgSwitcherMenu() {
const { organizations, setActiveOrganization } = useHelloJohn();
const { organization: activeOrg } = useOrganization();
return (
<div className="org-switcher">
<div className="current-org">
<img src={activeOrg?.logo_url} alt="" />
<span>{activeOrg?.name ?? "Select organization"}</span>
</div>
<ul className="org-list">
{organizations.map((org) => (
<li key={org.id}>
<button
onClick={() => setActiveOrganization(org.id)}
className={org.id === activeOrg?.id ? "active" : ""}
>
{org.name}
{org.is_personal && " (Personal)"}
</button>
</li>
))}
<li>
<button onClick={() => window.location.href = "/orgs/new"}>
+ Create organization
</button>
</li>
</ul>
</div>
);
}