> ## Documentation Index
> Fetch the complete documentation index at: https://docs.userkit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Federado com Clerk

> O Clerk é dono da conta. Assine o userId no seu servidor e o UserKit confia.

O Clerk é a sua fonte da verdade. O UserKit precisa saber *qual* dos seus usuários
está na página, e não vai aceitar a palavra da página — o seu servidor assina a
afirmação.

<Warning>
  **O identity secret nunca entra no bundle.** Nunca prefixe com `NEXT_PUBLIC_`,
  nunca renderize no HTML, nunca mande para o navegador para economizar uma ida e
  volta. Quem tem esse segredo emite sessão **verificada** para qualquer um dos
  seus usuários.
</Warning>

O contrato é o da página de
[identidade federada](/pt-br/customer-auth/federated), e nada aqui o altera:

```
hash = hex( HMAC-SHA256( identity_secret, external_id ) )
```

## O que o Clerk fornece

Um valor: `userId`, o id `user_…` que os próprios webhooks do Clerk citam. É esse
o `external_id`.

```ts lib/clerk.ts theme={null}
import "server-only";

import { auth } from "@clerk/nextjs/server";

export async function currentExternalId(): Promise<string | null> {
  const { userId } = await auth();
  return userId ?? null; // ← o external_id
}
```

`auth()` lê a sessão que o middleware do Clerk já verificou. Decodificar o cookie
`__session` na mão e assinar o `sub` dele seria assinar um valor que o navegador
escolheu.

<Note>
  `auth()` exige `clerkMiddleware()` montado, senão ele **lança** em vez de
  responder `userId` nulo. Essa é a falha certa: "não consegui saber" nunca pode ser
  lido como "está deslogado".
</Note>

## A metade do servidor

```ts app/api/userkit-boot/route.ts theme={null}
import { createHmac } from "node:crypto";
import { NextResponse } from "next/server";

import { currentExternalId } from "@/lib/clerk";

export const dynamic = "force-dynamic";

export async function GET() {
  const externalId = await currentExternalId();
  if (!externalId) {
    return NextResponse.json({ error: "not_signed_in" }, { status: 401, headers: NO_STORE });
  }

  const hash = createHmac("sha256", process.env.USERKIT_IDENTITY_SECRET!)
    .update(externalId)
    .digest("hex");

  return NextResponse.json({ external_id: externalId, hash }, { headers: NO_STORE });
}

// Este corpo identifica uma pessoa. Um CDN que o guardasse entregaria ao próximo
// visitante a prova da identidade de outra pessoa.
const NO_STORE = { "Cache-Control": "no-store" };
```

## A metade do cliente

```tsx theme={null}
"use client";

import { createClient } from "@userkit/js";

const userkit = createClient({
  publishableKey: process.env.NEXT_PUBLIC_USERKIT_PUBLISHABLE_KEY!,
});

const response = await fetch("/api/userkit-boot", { cache: "no-store" });

if (response.status === 401) {
  // Continua sendo um visitante. O boot anônimo é onde a atribuição de primeiro
  // toque cai.
  await userkit.boot();
} else {
  const { external_id, hash } = await response.json();
  await userkit.boot({ externalId: external_id, hash });
}
```

`getState().verified` só é `true` quando o HMAC conferiu. Logado no Clerk com
`verified: false` significa que o segredo é do outro ambiente ou que a mensagem
assinada não era exatamente o `external_id`.

## Configuração

| Variável                                                | Onde vive                                                                           |
| ------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `USERKIT_IDENTITY_SECRET`                               | **Só no servidor.** `GET /v1/organization/environments/{id}/identity`, por ambiente |
| `NEXT_PUBLIC_USERKIT_PUBLISHABLE_KEY`                   | No navegador. Identifica o ambiente e não autentica nada                            |
| `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`, `CLERK_SECRET_KEY` | O par do Clerk — o segundo só no servidor, igual ao do UserKit                      |

## A organização do Clerk não é a identidade

`orgId` é uma segunda dimensão, e assinar `userId + orgId` numa mensagem só
transformaria uma pessoa em dois contatos. O objeto de time do UserKit é o
[customer](/pt-br/concepts/customer-teams), resolvido por requisição a partir do
`X-Customer-Id` — mapeie a organização do Clerk nele, não no `external_id`.

## O que não viaja

Um usuário do Clerk carrega endereços de e-mail verificados. Eles não viram
identidade no UserKit: um e-mail enviado pelo `/v1/boot` é guardado como atributo,
não resolve para um contato existente e não vira aresta de identidade. O HMAC prova
o `external_id` e só ele.

Para provar um endereço, mande um [magic link ou um código por
e-mail](/pt-br/customer-auth/email-proof) — os dois fluxos que chegam nele.

<Card title="O exemplo completo" icon="github" href="https://github.com/userkithq/monorepo/tree/main/examples/federated-clerk">
  Um app Next executável com esses arquivos, um `.env.example` e as partes que esta
  página deixa de fora.
</Card>
