> **Esta página faz parte do roteiro de integração.** Ela detalha a fase 4 (formato B: o botão "Entrar com Plicflix" fora do app). Para pôr um jogo no plicflix do zero até ele aparecer no catálogo, siga o roteiro em ordem: https://developers.plicflix.com/docs/COMECE (fases 1 a 8, com quem faz cada passo). Índice para IAs: https://developers.plicflix.com/llms.txt

# Entrar com Plicflix (fora do app)

O botão **"Entrar com Plicflix"** no **seu** site ou app. O jogador entra com a
conta do plicflix e o seu jogo recebe **o mesmo jogador** da entrada pelo app:
o mesmo `id` (`pf_…`, fixo por pessoa e por jogo) e o mesmo apelido. Assim, uma
conta do seu jogo serve às duas portas:

| porta | quando | como o jogo recebe o jogador |
|---|---|---|
| **pelo app** ([SDK](SDK)) | o jogador toca em Jogar no plicflix | token `pft_…` no endereço → `POST /v1/ingresso/trocar` |
| **fora do app** (esta página) | o jogador abriu o seu site direto, ou o seu app nativo | OAuth 2.0 + PKCE → `POST /v1/oauth/token` |

É OAuth 2.0 padrão (RFC 6749), com **PKCE S256 obrigatório** (RFC 7636). Qualquer
biblioteca de OAuth serve. O plicflix **não** emite `id_token`: o jogador vem
direto na resposta do token.

> **Para IAs:** siga as seções 1 a 5 na ordem. A seção 4 é o caminho
> recomendado (com servidor). O exemplo inteiro, em Node, está na seção 4.3.
> Os ajudantes prontos ficam em [`plicflix.mjs`](/sdk/plicflix.mjs?baixar=1) e
> [`plicflix.go`](/sdk/plicflix.go?baixar=1) (servidor) e
> [`plicflix.js`](/sdk/plicflix.js?baixar=1) 2.4+ (navegador).

## 1. Os endereços

| o quê | endereço |
|---|---|
| autorizar (o navegador vai até lá) | `https://plicflix.com/oauth/authorize` |
| trocar o código | `POST https://api.plicflix.com/v1/oauth/token` |
| quem é (com o access token) | `GET https://api.plicflix.com/v1/oauth/userinfo` |
| revogar o access token | `POST https://api.plicflix.com/v1/oauth/revogar` |
| descoberta (RFC 8414) | `https://plicflix.com/.well-known/oauth-authorization-server` |
| `client_id` | o **slug** do seu jogo |
| `client_secret` (opcional) | uma **chave `pk_…` deste jogo** (ou a antiga do estúdio) |
| escopo | `perfil` (`openid` e `profile` são aceitos como sinônimos) |

## 2. Cadastrar os endereços de volta

No portal: **developers.plicflix.com → o seu jogo → Integração → Entrar com
Plicflix**, um `redirect_uri` por linha (até 10). Ou pela API, com a chave:

```bash
curl -X PUT https://api.plicflix.com/v1/parceiro/jogos/SEU-SLUG/oauth \
  -H "Authorization: Bearer pk_…" -H "Content-Type: application/json" \
  -d '{"redirect_uris":["https://seujogo.com/api/plicflix/volta","http://localhost:5173/api/plicflix/volta"]}'
# → {"client_id":"SEU-SLUG","redirect_uris":[…],"authorization_endpoint":…,"token_endpoint":…}
```

As regras:
- `https://…`. `http://` só vale em `localhost`, `127.0.0.1` e `::1` (para testar).
- App nativo: um esquema próprio com ponto no nome (`com.estudio.jogo:/oauth`,
  RFC 8252).
- Sem `#`. A comparação é **exata**, caractere por caractere, inclusive a query.
- Vale em qualquer situação do jogo: rascunho, revisão ou publicado. O login é
  do **seu site**, não do catálogo. O que se exige é o estúdio **ativo** (taxa
  de entrada paga, não suspenso). Sem isso, a página mostra o erro e não volta.

## 3. O fluxo

```
seu jogo                         navegador                       plicflix
   │ 1. gera verifier + state       │                                │
   │──── 302 /oauth/authorize ─────▶│──────────────────────────────▶ │ 2. entra (se precisar)
   │                                │                                │    e autoriza (1ª vez)
   │◀──── redirect_uri?code&state ──│◀────────────────────────────── │
   │ 3. confere o state                                              │
   │──── POST /v1/oauth/token (code + verifier [+ pk_]) ───────────▶ │
   │◀─── { jogador: { id: "pf_…", nome }, jogo, assinante, access_token } │
   │ 4. acha/cria a conta por jogador.id e abre a SUA sessão         │
```

**1. Mandar o navegador** para:

```
https://plicflix.com/oauth/authorize?response_type=code
  &client_id=SEU-SLUG
  &redirect_uri=https%3A%2F%2Fseujogo.com%2Fapi%2Fplicflix%2Fvolta
  &state=ALEATORIO            ← guarde (cookie curto); confira na volta
  &code_challenge=BASE64URL(SHA256(verifier))
  &code_challenge_method=S256
  &scope=perfil
  [&prompt=consent]           ← pergunta de novo mesmo se já autorizou
```

O `verifier` tem de 43 a 128 caracteres de `[A-Za-z0-9-._~]` (por exemplo, 48
bytes aleatórios em base64url).

**2. O plicflix cuida do resto:**
- quem já está logado no plicflix nesse navegador não digita nada;
- quem não está entra, ou cria a conta e confirma o e-mail, ali mesmo;
- da segunda vez em diante a autorização é automática: o jogador nem vê a tela.

A tela mostra o que o jogo recebe (o apelido, um id só deste jogo e se é
assinante) e o que **nunca** recebe (o e-mail, a senha, os amigos).

**3. A volta.** O navegador chega em `redirect_uri?code=pfc_…&state=…`. Se o
jogador cancelou, chega com `error=access_denied&state=…`. O código vale **5
minutos e uma troca**.

Um pedido malformado volta com `error=invalid_request`, `invalid_scope` ou
`unsupported_response_type`. Se o `client_id` ou o `redirect_uri` não
conferem, o plicflix **não** volta: mostra o erro na própria página.

**4. A troca**, de preferência no seu servidor:

```bash
curl -X POST https://api.plicflix.com/v1/oauth/token \
  -u "SEU-SLUG:pk_…" \
  -d grant_type=authorization_code -d code=pfc_… \
  -d redirect_uri=https://seujogo.com/api/plicflix/volta -d code_verifier=…
```

```json
{
  "jogador": { "id": "pf_3k2j…", "nome": "Ana", "avatar": "https://midia.plicflix.com/midia/avatares/aurea-lena.png" },
  "jogo": "seu-slug",
  "assinante": true,
  "sub": "pf_3k2j…",
  "name": "Ana",
  "picture": "https://midia.plicflix.com/midia/avatares/aurea-lena.png",
  "access_token": "pfa_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "perfil"
}
```

- **Quem entra é o perfil** escolhido no plicflix daquele aparelho (cada
  conta tem até 5). `jogador.avatar` (e `picture`, no padrão OpenID) é a
  imagem dele: URL `https` pública, 256×256. Sem imagem, `avatar` vem `""` e
  `picture` não vem.
- O corpo pode ser `application/x-www-form-urlencoded` (o padrão) ou JSON.
- A chave vai como Basic (`client_id:pk_…`), como `client_secret` no corpo ou
  como `Authorization: Bearer pk_…`.
- **Sem a chave**, o pedido é de um cliente público (o jogo sem servidor, o app
  nativo): o PKCE é a prova.
- `jogador.id` é **igual** ao da entrada pelo app (`/v1/ingresso/trocar`):
  ligue a conta por ele.

**5. Depois:** o `access_token` vale **1 hora** e só serve para
`GET /v1/oauth/userinfo`, que devolve as mesmas chaves (sem o token). Não há
refresh token: o seu jogo abre a **própria** sessão. Para saber de novo quem
é, repita o fluxo; da segunda vez em diante ele é automático e invisível.

## 4. Com servidor (recomendado)

### 4.1 As duas rotas

- `GET /api/plicflix/oauth`:
  1. gere `verifier` e `state` (`novoPKCE()`);
  2. guarde os dois num cookie `HttpOnly; Secure; SameSite=Lax` de 10 minutos;
  3. responda `302` para `urlDeEntrada(...)`.
- `GET /api/plicflix/volta`:
  1. confira `state` contra o cookie e apague o cookie;
  2. se veio `error`, mande o jogador de volta à sua tela de login;
  3. chame `trocarCodigo(code, …)` com a chave `pk_`;
  4. ache ou crie a conta por `jogador.id`, abra a sessão e responda `302` para
     o jogo.

A conta segue as mesmas regras da entrada pelo app ([SDK](SDK#a-conta)): coluna
única `plicflix_id`, login interno reservado (`pf_…@plicflix.invalid`) e apelido
atualizado a cada entrada.

### 4.2 O botão

O botão oficial, como link para a sua rota:

```html
<script src="/plicflix.js" defer></script>
<plicflix-entrar href="/api/plicflix/oauth" largura="100%"></plicflix-entrar>
```

Tem os estilos `destaque`, `solido`, `simples` e o seu próprio (`proprio`),
explicados com todas as opções e as regras da marca em [BOTAO](BOTAO.md). O
código já preenchido com o seu jogo sai da página [Botão oficial](/botao).

### 4.3 Exemplo completo (Node 18+, sem dependências)

```js
import { createServer } from "node:http";
import { novoPKCE, urlDeEntrada, trocarCodigo } from "./plicflix.mjs"; // PLICFLIX_CHAVE no ambiente

const CLIENTE = "seu-slug";
const VOLTA = "https://seujogo.com/api/plicflix/volta";
const cookies = (req) => Object.fromEntries((req.headers.cookie || "").split(/; */).filter(Boolean).map((c) => c.split("=").map(decodeURIComponent)));

createServer(async (req, res) => {
  const url = new URL(req.url, "https://seujogo.com");
  if (url.pathname === "/api/plicflix/oauth") {
    const { verificador, desafio, estado } = novoPKCE();
    res.writeHead(302, {
      "Set-Cookie": `pf_oauth=${estado}.${verificador}; Path=/api/plicflix; HttpOnly; Secure; SameSite=Lax; Max-Age=600`,
      Location: urlDeEntrada({ cliente: CLIENTE, volta: VOLTA, desafio, estado }),
    });
    return res.end();
  }
  if (url.pathname === "/api/plicflix/volta") {
    const [estado, verificador] = (cookies(req).pf_oauth || ".").split(".");
    const apagar = "pf_oauth=; Path=/api/plicflix; Max-Age=0";
    if (!estado || url.searchParams.get("state") !== estado || !url.searchParams.get("code")) {
      res.writeHead(302, { "Set-Cookie": apagar, Location: "/sign-in?erro=plicflix" });
      return res.end();
    }
    const r = await trocarCodigo(url.searchParams.get("code"), { cliente: CLIENTE, volta: VOLTA, verificador });
    const conta = await acharOuCriarConta(r.jogador.id, r.jogador.nome); // a SUA conta
    res.writeHead(302, { "Set-Cookie": [apagar, await abrirSessao(conta)], Location: "/play" });
    return res.end();
  }
}).listen(8080);
```

Em Go é o mesmo, com `plicflix.NovoPKCE()`, `plicflix.URLDeEntrada(cliente,
volta, pk)` e `cliente.TrocarCodigo(ctx, cliente, code, volta, verificador)`.
Os dois ajudantes estão em [`plicflix.go`](/sdk/plicflix.go?baixar=1).

## 5. Sem servidor (jogo só de navegador)

O SDK faz tudo:

```html
<script src="/plicflix.js"></script>
<div id="entrar"></div>
<script type="module">
  const CLIENTE = "seu-slug", VOLTA = location.origin + "/";
  const r = await Plicflix.voltouDoPlicflix();        // na volta: troca direto na API (cliente público)
  if (r) iniciar(r.jogador);                          // { id: "pf_…", nome }
  else Plicflix.botaoEntrar("#entrar", { cliente: CLIENTE, volta: VOLTA });   // = <plicflix-entrar cliente volta>
</script>
```

- `voltouDoPlicflix()` confere o `state`, troca o código e limpa a URL.
- Com `{ servidor: "/sua/rota" }`, ele manda `{ code, code_verifier,
  redirect_uri }` ao seu servidor em vez de trocar direto. É o meio-termo para
  quem já tem uma página e quer trocar no servidor.
- Como tudo o que roda no navegador, o resultado de um cliente público serve
  para mostrar o nome e guardar o progresso local. **Nunca** use para proteger
  moedas, ranking ou compras: para isso, troque no servidor (seção 4).

## 6. Segurança (confira cada uma)

- [ ] O `state` é aleatório, fica num cookie `HttpOnly` e é conferido na volta.
- [ ] O `verifier` nunca sai do seu lado: só o `code_challenge` vai na URL.
- [ ] A chave `pk_…` só existe no servidor (nunca no navegador nem no app).
- [ ] A conta é achada por `jogador.id`, nunca pelo apelido (o apelido muda).
- [ ] O login interno `…@plicflix.invalid` é recusado no seu cadastro normal.
- [ ] Os `redirect_uri` de `localhost` saem da lista antes de publicar, se você
      não quiser que funcionem em produção.

## 7. Erros

O token responde no formato do OAuth: `{"error": "...", "error_description": "..."}`.

| status | `error` | quando |
|---|---|---|
| `400` | `invalid_grant` | o código é inválido, venceu (5 min), já foi usado ou é de outro jogo; ou o `redirect_uri` é diferente; ou o `code_verifier` não confere. Um código que falha **morre**: recomece o fluxo. |
| `400` | `invalid_request` | falta `code` ou `code_verifier`, ou o corpo é inválido |
| `400` | `unsupported_grant_type` | só `authorization_code` |
| `401` | `invalid_client` | o `client_id` não existe ou a chave não é deste jogo (nem a antiga do estúdio dono) |
| `401` | `invalid_token` | userinfo: o `pfa_` é inválido ou venceu |
| `429` | — | limite por IP; espere um minuto |

Na volta para o seu `redirect_uri`, os erros possíveis são `access_denied` (o
jogador cancelou), `invalid_request` (faltou o PKCE S256), `invalid_scope` e
`unsupported_response_type`.

## 8. Testar

1. Cadastre `http://localhost:PORTA/api/plicflix/volta` (seção 2).
2. Use uma conta **testadora** do jogo (com o e-mail confirmado) se ele ainda
   não está publicado.
3. Abra `http://localhost:PORTA/api/plicflix/oauth` e entre: você volta logado
   no seu jogo. O `jogador.id` é o mesmo de quando a mesma conta abre o jogo
   pelo app.
4. Abra de novo: desta vez a volta é direta, sem a tela de autorizar. Para ver
   a tela outra vez, use `prompt=consent`.
