Organizations
Let users create teams, invite people by email, and give them roles, like a workspace in Slack or a team in Vercel.
An organization is a group of users that share something: a workspace, a company account, a team. Each organization has members, and every member has a role.
| Role | Can do |
|---|---|
admin |
Rename or delete the organization, invite people, change roles, remove members |
member |
See the organization and its members, and leave |
Every organization always has at least one admin: the last admin can't be demoted, removed or leave until someone else is promoted.
How it works for your users
- A signed-in user creates an organization and becomes its admin.
- An admin enters a teammate's email. We email them an invitation link (valid for 7 days).
- The teammate opens the link, signs in or signs up with that same email address, and accepts.
- Anyone with several organizations can switch the active organization.
The invitation page is hosted for you at /accept-invite, so there's nothing to build.
Security note. An invitation can only be accepted by the invited email address, and only after that address is verified. A forwarded or leaked link is useless to anyone else. Re-inviting an address replaces the earlier link.
Create and list (in the browser)
"use client";
import { useAuthClient } from "@daywalker/auth/react";
export function Teams() {
const auth = useAuthClient();
async function create() {
const org = await auth.createOrganization("Acme Inc");
await auth.setActiveOrganization(org.id); // work inside it right away
}
async function invite(orgId: string) {
await auth.inviteToOrg(orgId, "[email protected]", "member");
}
return <button onClick={create}>New team</button>;
}All the client methods are listed in the client reference.
Know which organization the user is in (on your server)
When a user has an active organization, their session token carries two extra claims, org_id and org_role. They're signed, so you can trust them without calling us.
// Next.js: app/api/projects/route.ts
import { auth } from "@daywalker/auth/nextjs";
export async function GET() {
const { userId, orgId, orgRole } = await auth();
if (!userId) return new Response("Unauthorized", { status: 401 });
if (!orgId) return new Response("Pick an organization first", { status: 400 });
// Always scope your data by orgId.
const projects = await db.projects.where({ orgId });
return Response.json({ projects, canEdit: orgRole === "admin" });
}verifyToken() from @daywalker/auth/server returns the same orgId and orgRole fields for other frameworks.
Roles can change. A token lives 60 seconds, and we re-check the user's role whenever one is minted, so a removed member loses access within about a minute. For very sensitive actions, confirm with the backend API.
Backend API
With your secret key you can list organizations and their members:
import { createBackendClient } from "@daywalker/auth/server";
const backend = createBackendClient({ secretKey: process.env.DAYWALKER_SECRET_KEY!, apiUrl: process.env.DAYWALKER_API_URL! });
const orgs = await backend.organizations.list();
const members = await backend.organizations.members(orgs[0].id); // [{ userId, role, joinedAt }]Organizations are scoped to your instance: another app's secret key can never see them.
Requiring two-factor
An organization can require two-step verification for its members: setOrganizationTwoFactor(orgId, true) in the browser (admins only, and they need it on themselves first) or PATCH /v1/organizations/{orgId} { "requireTwoFactor": true } from your backend. Members without it don't get that organization's claims in their token until they turn it on. See Requiring 2FA.
Good to know
- Non-members get a
404for an organization, so its existence isn't revealed. - Deleting an organization removes its memberships and invitations.
- Invitation emails are sent through your configured mail transport.