Receita — Setup multi-conta (organizações)
Cenário: seu SaaS permite que usuários criem organizações e convidem membros. Cada organização é uma Account no Auth Platform. O mesmo usuário pode ser admin de "Empresa X" e membro de "Empresa Y".
Diferença entre 1:1 e multi-conta
| 1:1 (padrão) | Multi-conta | |
|---|---|---|
| Contas por usuário | 1 (auto-criada no cadastro) | N (usuário cria) |
| Quem adiciona membros | Só o Platform Admin | Admin da conta (via sua API) |
account_id no token | Sempre a conta pessoal | A conta selecionada no login |
| Seletor de conta no login | Não aparece | Aparece quando tem > 1 conta |
Passo 1 — Criar o sistema com multiAccount=true
SYSTEM=$(curl -s -X POST http://localhost:4000/admin/systems \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Financeiro SaaS",
"redirectUris": ["http://localhost:3000/callback"],
"allowedScopes": ["openid","email","profile","offline_access"],
"multiAccount": true, # ← habilita múltiplas contas
"allowSelfRegister": true,
"requireAdminActivation": false,
"allowClientCredentials": true # ← para chamadas M2M da sua API
}')
SYSTEM_ID=$(echo $SYSTEM | jq -r '.id')
CLIENT_ID=$(echo $SYSTEM | jq -r '.clientId')
CLIENT_SECRET=$(echo $SYSTEM | jq -r '.clientSecret') # guarde — aparece uma vez
Passo 2 — Criar as roles da organização
# Admin da conta — pode convidar membros
ADMIN_ROLE=$(curl -s -X POST http://localhost:4000/admin/systems/$SYSTEM_ID/roles \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"admin","isAdmin":true,"scopes":["fin:admin","fin:read","fin:write"]}')
ADMIN_ROLE_ID=$(echo $ADMIN_ROLE | jq -r '.id')
# Membro comum
MEMBER_ROLE=$(curl -s -X POST http://localhost:4000/admin/systems/$SYSTEM_ID/roles \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"membro","isAdmin":false,"scopes":["fin:read","fin:write"]}')
MEMBER_ROLE_ID=$(echo $MEMBER_ROLE | jq -r '.id')
Passo 3 — Usuário cria uma organização (via sua API)
Quando o usuário quer criar uma nova organização, sua API faz a chamada com um service token (client_credentials):
// Sua API: POST /orgs { name: "Empresa X Ltda" }
// req.user.sub = userId do usuário autenticado (do JWT)
async function createOrganization(req: any, res: any) {
const userId = req.user.sub; // do JWT do usuário
const { name } = req.body;
// Sua API chama o Auth Platform com um service token
const serviceToken = await getServiceToken(); // client_credentials
const mbRes = await fetch('http://localhost:4000/admin/memberships', {
method: 'POST',
headers: {
Authorization: `Bearer ${serviceToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId,
systemId: process.env.SYSTEM_ID,
accountName: name, // ← nome da organização
roles: [process.env.ADMIN_ROLE_ID], // ← o criador vira admin
}),
});
const { account } = await mbRes.json() as any;
// Opcional: inicializar dados da org na sua base
await db.organization.create({
data: { authAccountId: account.id, name, ownerId: userId },
});
res.status(201).json({ orgId: account.id, name });
}
Passo 4 — Seletor de conta no login
Quando o usuário tem > 1 conta, o Auth Platform mostra automaticamente o seletor de conta na tela de login.
Para pré-selecionar uma conta (trocar de organização sem logout):
// Iniciar login já selecionando uma conta específica
function switchOrganization(accountId: string) {
userManager.signinRedirect({
extraQueryParams: { account_id: accountId },
});
}
// Após o login, o token terá:
// payload.account_id = accountId escolhida
// payload.roles = roles nesta conta específica
// payload.is_admin = true/false para esta conta
Passo 5 — Convidar membro para a organização
// Sua API: POST /orgs/:accountId/members { email, roleId }
// Requer: req.user.is_admin === true E req.user.account_id === accountId
async function inviteMember(req: any, res: any) {
const { accountId } = req.params;
const { email, roleId } = req.body;
// Validar que o solicitante é admin DESTA conta
if (!req.user.is_admin || req.user.account_id !== accountId) {
return res.status(403).json({
error: 'Apenas o admin desta organização pode convidar membros',
});
}
const serviceToken = await getServiceToken();
const headers = { Authorization: `Bearer ${serviceToken}`, 'Content-Type': 'application/json' };
// Buscar userId pelo e-mail
const search = await fetch(
`http://localhost:4000/admin/users?search=${encodeURIComponent(email)}`,
{ headers }
).then(r => r.json()) as any;
if (!search.data?.length) {
return res.status(404).json({ error: 'Usuário não encontrado na plataforma' });
}
const userId = search.data[0].id;
// Conceder acesso à conta compartilhada do admin
await fetch('http://localhost:4000/admin/memberships', {
method: 'POST',
headers,
body: JSON.stringify({
userId,
accountId, // ← conta existente do admin
roles: [roleId ?? process.env.MEMBER_ROLE_ID],
}),
});
res.status(201).json({ message: 'Membro adicionado com sucesso' });
}
Passo 6 — Isolamento de dados por organização
Sempre filtre pelo account_id do token — nunca pelo sub:
// Middleware para extrair a conta ativa
function getActiveAccount(req: any): string {
return req.user.account_id; // do JWT — imutável por request
}
// ✅ Correto — dados isolados por organização
app.get('/transacoes', authMiddleware, async (req, res) => {
const accountId = getActiveAccount(req);
const data = await db.transaction.findMany({
where: { accountId }, // ← filtra pela conta do token
});
res.json(data);
});
// ❌ Errado — mistura dados de todas as organizações do usuário
app.get('/transacoes', authMiddleware, async (req, res) => {
const data = await db.transaction.findMany({
where: { userId: req.user.sub }, // ← ERRADO
});
});
Passo 7 — Remover membro
// Sua API: DELETE /orgs/:accountId/members/:membershipId
async function removeMember(req: any, res: any) {
const { accountId, membershipId } = req.params;
// Validar que é admin desta conta
if (!req.user.is_admin || req.user.account_id !== accountId) {
return res.status(403).json({ error: 'Sem permissão' });
}
const serviceToken = await getServiceToken();
await fetch(`http://localhost:4000/admin/memberships/${membershipId}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${serviceToken}` },
});
// → Auth Platform emite webhook user.access_revoked
res.status(204).send();
}
Checklist
- Sistema com
multiAccount: trueeallowClientCredentials: true - Service token configurado para chamadas da sua API ao Auth Platform
- Sua API cria a organização via
/admin/memberships(não o frontend) - Dados filtrados por
account_idem todas as queries - Validação
req.user.account_id === params.accountIdantes de operações sensíveis - Webhook configurado para provisionar/desprovisionar dados
Exemplo funcional:
examples/03-multi-contacom servidor Express completo