Pular para o conteúdo
🔄 GUIAS

Migrar do Auth0 pro SuperDB Auth.

Auth0 cobra por MAU em USD e fica caro rápido. O SuperDB Auth é compatível em features (OAuth, MFA, SSO, SCIM, custom claims) e ainda tem CPF/CNPJ + WhatsApp OTP. Este guia mostra como migrar usuários sem perda.

Por que migrar do Auth0?

O Auth0 (hoje parte da Okta) é um produto sólido, mas três coisas pesam pra times brasileiros:

  • Preço em USD por MAU. O plano "Essentials" começa em US$ 35/mês com 500 MAU; a partir de 1k MAU, o plano "Professional" sobe pra US$ 240/mês e segue escalando linearmente. Pra um SaaS B2C com 50k usuários ativos, a conta passa fácil de R$ 5.000/mês.
  • Lock-in. Custom claims, Rules e Actions são escritos no painel deles. Migrar dali é trabalhoso por design.
  • Features brasileiras. Sem CPF/CNPJ, sem WhatsApp OTP, sem suporte em PT-BR pra dúvidas de LGPD. O SuperDB nasceu com isso.

A boa notícia: features de autenticação em SaaS B2B são bem padronizadas. Migrar dá trabalho mas é determinístico.

Compatibilidade de features

FeatureAuth0SuperDB AuthObservação
Email + senhabcrypt do Auth0 importa direto
OAuth social (Google, GitHub, Apple, Microsoft)Reconfigurar callback URLs
Magic link / passwordlessAPI equivalente
MFA TOTPRe-enrollment recomendado (Passo 5)
MFA WebAuthn / Passkeyem breveSprint 9+
MFA SMSvia WhatsApp OTPWhatsApp em vez de SMS (mais barato no BR)
Custom claims✓ (Actions)✓ (JWT Hooks)Actions JS → SQL functions
RBAC✓ (Roles + Permissions)✓ (claims + RLS)Pattern diferente, mesmo efeito
SCIM 2.0Sprint 5
SAML SSO (Enterprise)Sprint 3B
Organizations / Multi-tenantSprint 3A
CPF / CNPJ validationBuilt-in, dígito verificador
WhatsApp OTPTwilio Business API integrado

Passo 1: export do Auth0

O Auth0 expõe duas rotas: o CLI (mais simples) ou a Management API (mais flexível pra projetos grandes). O CLI cobre 95% dos casos.

Terminal
# 1. Instale o CLI
npm install -g auth0-cli

# 2. Faça login (abre o browser)
auth0 login

# 3. Export
auth0 users export --tenant [TENANT] --format json --output users.json

O users.json vem assim:

users.json (trecho)
[
  {
    "user_id": "auth0|65...",
    "email": "alice@example.com",
    "email_verified": true,
    "password_hash": "$2b$10$...",          // bcrypt
    "created_at": "2024-08-15T10:30:00.000Z",
    "user_metadata": { "cpf": "123.456.789-00" },
    "app_metadata":  { "role": "admin" },
    "identities": [
      { "provider": "auth0",      "user_id": "65...", "isSocial": false },
      { "provider": "google-oauth2", "user_id": "1100...", "isSocial": true }
    ],
    "multifactor": ["guardian"]              // TOTP via Auth0 Guardian
  }
]
⚠️

Auth0 não exporta hashes de senha por padrão. O password_hash só vem no export se o tenant estiver com a flag "Allow Password Hash Export" habilitada (Dashboard → Tenant Settings → Advanced). Sem isso, você só consegue o hash via "User Migration" automatic via API (custom DB), o que é mais trabalhoso. Habilite a flag antes do export.

Passo 2: import via MCP / service-role

Use o service-role key do SuperDB pra criar usuários em batch. Cada usuário pode ter senha hash externa — o SuperDB aceita bcrypt diretamente.

scripts/import-auth0.ts
import { createClient } from '@superdb/supabase-compat'
import users from './users.json' assert { type: 'json' }

const db = createClient(
  process.env.SUPERDB_URL!,
  process.env.SUPERDB_SERVICE_ROLE!
)

let ok = 0, fail = 0

for (const u of users) {
  const { error } = await db.auth.admin.createUser({
    email: u.email,
    email_confirm: u.email_verified,
    password_hash: u.password_hash,        // bcrypt $2b$ vai direto
    user_metadata: {
      ...u.user_metadata,
      auth0_user_id: u.user_id,
      providers: u.identities.map((i) => i.provider),
    },
    app_metadata: u.app_metadata,
  })

  if (error) { console.error(`[fail] ${u.email}: ${error.message}`); fail++ }
  else { console.log(`[ok]   ${u.email}`); ok++ }
}

console.log(`\nDone: ${ok} importados, ${fail} falhas`)
💡

Use a MCP do SuperDB pra migração assistida por LLM: se você está usando Claude Code, Cursor ou outra ferramenta com MCP, conecte o servidor @superdb/mcp e peça pro modelo importar o JSON. A ferramenta superdb_create_user aceita os mesmos campos e dá feedback estruturado por usuário. Útil pra investigar falhas (ex: emails duplicados).

Passo 3: senhas (importante)

Auth0 usa bcrypt com custo configurável (default 10). O SuperDB Auth aceita bcrypt nativamente e re-hash lazy pra Argon2id no próximo login bem-sucedido. Na prática, ninguém precisa resetar senha — o login com a senha antiga funciona e o hash migra silenciosamente.

Como verificar que está funcionando:

  1. Faça login com um usuário recém-importado.
  2. Olhe a coluna encrypted_password dele no banco: o prefixo muda de $2b$... (bcrypt) pra $argon2id$....
  3. O last_sign_in_at também é atualizado.
ℹ️

E quem entra só via OAuth? Esses usuários não têm password_hash no export e ficam sem senha no SuperDB. Eles continuam entrando pelo provider social (Passo 4). Se você quiser permitir que setem uma senha depois, mande um email "Defina sua senha" com magic link → updateUser({ password }).

Passo 4: OAuth providers

OAuth não migra automático — você precisa reconfigurar cada provider no SuperDB Dashboard e atualizar as callback URLs no console do provider (Google Cloud, GitHub Developer Settings, Apple Developer, Azure AD).

Você tem 2 escolhas pra cada provider:

OpçãoQuando usarTrade-off
Reutilizar o mesmo client_idQuiser que os usuários NÃO precisem reautorizar o appPrecisa adicionar a callback URL do SuperDB ao app OAuth existente
Criar client_id novoQuiser separação limpa (auditoria, lifecycle)Usuários veem novamente a tela "este app quer acessar…"

A nova callback URL é: https://api.superdb.com.br/auth/v1/callback. Adicione ela na lista de "Authorized redirect URIs" do provider e configure no SuperDB Dashboard → Auth → Providers → Google (ou outro).

O link entre usuário e identidade externa fica em auth.identities. O script de import já popula user_metadata.providers com a lista de providers que o usuário usou no Auth0 — você pode usar isso pra mostrar uma mensagem amigável tipo "você entrou antes com Google; entrar de novo com Google?".

Passo 5: MFA TOTP

Aqui está a parte chata. Os secrets TOTP no Auth0 ficam criptografados no Auth0 Guardian e não saem no export. Duas saídas práticas:

  1. Re-enrollment na primeira sessão pós-migração (recomendado). No primeiro login depois da migração, force o usuário com MFA-on a re-cadastrar o autenticador (Google Authenticator, 1Password, etc). Mostre uma tela com QR code novo gerado pelo SuperDB. UX: ~30 segundos a mais; segurança: melhor (rotaciona o secret).
  2. Import via API privada do Auth0 (raro). Pra contratos Enterprise, o Auth0 permite exportar os MFA factors via Management API com um token especial. Se você precisa disso (lista de usuários MFA com milhares de pessoas), entre em contato com contato@superdb.com.br — a gente tem um script que adapta.

Pra opção (1), o flow é:

app/login/route.ts (pseudo-código)
const { data, error } = await db.auth.signInWithPassword({ email, password })
if (error) return { error }

// Usuário tem flag de MFA do Auth0 mas ainda não enrollou no SuperDB
const hadMfaInAuth0 = data.user.user_metadata?.providers?.includes('guardian')
const enrolledInSuperDB = data.user.factors?.length > 0

if (hadMfaInAuth0 && !enrolledInSuperDB) {
  return redirect('/setup-mfa')  // tela com QR code novo
}

Passo 6: custom claims & JWT hooks

Auth0 "Actions" (e o legado "Rules") são funções JS que rodam no login pra adicionar custom claims no JWT. O equivalente no SuperDB são JWT Hooks: funções SQL invocadas a cada emissão de token.

Auth0 Action (antes) → SuperDB JWT Hook (depois)
// ────────── ANTES: Auth0 Action ──────────
exports.onExecutePostLogin = async (event, api) => {
  const role = event.user.app_metadata.role ?? 'user'
  api.idToken.setCustomClaim('https://app.com/role', role)
  api.accessToken.setCustomClaim('https://app.com/role', role)
}

-- ────────── DEPOIS: SuperDB JWT Hook (SQL) ──────────
create function public.custom_access_token_hook(event jsonb)
returns jsonb language plpgsql as $$
declare
  claims jsonb;
  user_role text;
begin
  select raw_app_meta_data ->> 'role' into user_role
    from auth.users
    where id = (event ->> 'user_id')::uuid;

  claims := event -> 'claims';
  claims := jsonb_set(claims, '{role}', to_jsonb(coalesce(user_role, 'user')));

  return jsonb_set(event, '{claims}', claims);
end $$;

-- registrar o hook
update auth.config set custom_access_token_hook = 'public.custom_access_token_hook';

Passo 7: RBAC / permissões

Auth0 RBAC: você define Roles com um conjunto de Permissions. O middleware do app verifica permissions a cada request. No SuperDB, o pattern equivalente combina claim no JWT + RLS policy no banco:

SQL — pattern RBAC com RLS
-- Hook injeta a role no JWT (Passo 6)
-- Aqui só a policy que checa

create policy "admins veem tudo, outros só os próprios"
  on public.orders
  for select
  using (
    (auth.jwt() ->> 'role') = 'admin'
    or user_id = auth.uid()
  );

create policy "só admins podem deletar"
  on public.orders
  for delete
  using ((auth.jwt() ->> 'role') = 'admin');
💡

Vantagem do pattern do SuperDB: a permissão fica no banco, não no middleware. Se você esquecer de checar a permission em algum endpoint novo, o banco recusa a query mesmo assim. No Auth0 RBAC tradicional, esquecer um check no middleware = vazamento de dados.

Padrão zero-downtime

Pra apps grandes, virar tudo de um dia pro outro é arriscado. O pattern recomendado é dual-write com fallback: durante 1-2 semanas, o app autentica primeiro no SuperDB; se o user ainda não foi migrado (404 ou senha errada), faz fallback pro Auth0 e migra o usuário no momento do login bem-sucedido.

app/login/route.ts — middleware zero-downtime
import { createClient } from '@superdb/supabase-compat'
import { AuthenticationClient } from 'auth0'

const sup = createClient(
  process.env.SUPERDB_URL!,
  process.env.SUPERDB_SERVICE_ROLE!
)

const auth0 = new AuthenticationClient({
  domain: process.env.AUTH0_DOMAIN!,
  clientId: process.env.AUTH0_CLIENT_ID!,
})

export async function login(email: string, password: string) {
  // 1. Tenta no SuperDB primeiro
  const sdr = await sup.auth.signInWithPassword({ email, password })
  if (sdr.data.session) return sdr.data

  // 2. Fallback: tenta no Auth0
  let a0
  try {
    a0 = await auth0.oauth.passwordGrant({ username: email, password })
  } catch {
    // senha errada nos dois → bloqueia
    return { error: { message: 'Credenciais inválidas' } }
  }

  // 3. Login no Auth0 OK → migra o user pro SuperDB agora
  const profile = await auth0.users.getInfo(a0.access_token)
  await sup.auth.admin.createUser({
    email: profile.email,
    password,                       // SuperDB vai hashar Argon2id
    email_confirm: profile.email_verified,
    user_metadata: { auth0_user_id: profile.sub, migrated_at: new Date().toISOString() },
  })

  // 4. Retorna sessão do SuperDB
  const final = await sup.auth.signInWithPassword({ email, password })
  return final.data
}

Depois de 2-4 semanas, 95%+ dos usuários ativos já migraram automaticamente no login. O resto você migra em batch (Passo 1 + 2) e desliga o fallback.

Checklist final

  • ☐ Flag "Allow Password Hash Export" habilitada no Auth0 antes do export
  • users.json exportado e validado (contém password_hash pra users com senha)
  • ☐ Script de import rodou; contagem bate (X usuários no Auth0 = X no SuperDB)
  • ☐ Senhas funcionando: login com user existente sem reset
  • ☐ OAuth providers reconfigurados (Google, GitHub, Apple, Microsoft) com callback nova
  • ☐ MFA: flag em user_metadata + tela de re-enrollment implementada
  • ☐ Custom claims migrados (Actions → JWT Hooks SQL)
  • ☐ RBAC traduzido pra claim + RLS policies
  • ☐ Zero-downtime middleware ativo (ou go-live agendada se vai fazer cut-over)
  • ☐ Smoke test: login, MFA, OAuth, refresh token, custom claims no JWT decodificado

Tem uma feature do Auth0 que eu uso e não está aqui?

Esse guia cobre 90% dos casos. As outras 10% são coisas como: bot detection, anomaly detection avançada, branded login com domínio custom, account linking automático entre providers, organizações com bulk import via SCIM.

Quase tudo isso já existe no SuperDB ou está no roadmap próximo (Sprints 7-9). Mande um email pra contato@superdb.com.br com a feature específica que você usa hoje — a equipe responde em até 1 dia útil dizendo (a) como mapear, (b) se já está pronto, ou (c) prazo realista pra entregar. Sem promessas mágicas.

Essa página ajudou?