DocumentaçãoPara quem hospeda
E-mail e login com Google
Como ligar a recuperação de senha por e-mail (SMTP) e o login com Google (OAuth), os dois recursos opcionais da conta, e o que configurar na tela de consentimento do Google.
A conta do OpENEM funciona só com e-mail e senha. Dois recursos dependem de serviço de fora e por isso são opcionais: a recuperação de senha por e-mail, que precisa de um servidor SMTP, e o login com Google, que precisa de um cliente OAuth no Google Cloud. Sem as variáveis, a tela de entrada esconde o Esqueci minha senha e o botão Entrar com Google, e o resto do app segue igual.
Trocar a senha e Sair de todos os dispositivos, na tela Perfil, não dependem de nada disso e funcionam em qualquer instalação.
Endereço público
Os dois recursos precisam da SITE_URL, o endereço que os alunos usam, como https://openem.exemplo. O link do e-mail e o endereço de volta do Google saem dela, e nunca do cabeçalho Host do pedido: quem forjasse o Host faria o e-mail de redefinição apontar para o site dele.
Em produção, sem SITE_URL, os dois recursos ficam desligados e a API deixa um aviso no log ao subir. Fora de produção, sem ela, a API usa http://localhost:5173, o endereço do npm run dev do frontend.
Use HTTPS: o link do e-mail carrega um segredo, e o cookie da ida ao Google só sai com a marca Secure quando a SITE_URL começa com https://. Veja Produção.
Recuperação de senha por e-mail
Como funciona
- Na tela de entrada, o aluno toca em Esqueci minha senha e digita o e-mail.
- A API responde sempre a mesma frase, tenha o e-mail conta ou não, e manda o e-mail em segundo plano: nem a resposta nem o tempo dela dizem quem tem conta.
- O e-mail traz um link para
/app/redefinir-senha. O link vale por 30 minutos e uma vez só, e pedir outro invalida o anterior. - Ao criar a senha nova, o aluno entra direto. Todas as outras sessões da conta são encerradas, o bloqueio por senhas erradas é liberado e o e-mail passa a contar como confirmado.
O token vai no fragmento do link (depois do #), que o navegador não manda a servidor nenhum, e o banco guarda só o hash SHA-256 dele. Os limites são 3 pedidos por hora para o mesmo e-mail, com ou sem conta, e 10 a cada 15 minutos por IP. A conta criada pelo Google, que não tem senha, usa o mesmo caminho para criar a primeira.
Configurar
No .env da raiz:
SITE_URL=https://openem.exemplo
SMTP_URL=smtps://usuario:senha@smtp.exemplo.com:465
MAIL_FROM="OpENEM <nao-responda@openem.exemplo>"
smtps://abre a conexão já com TLS, em geral na porta 465.smtp://começa sem TLS e passa para STARTTLS quando o servidor oferece, em geral na porta 587.- Usuário e senha com caracteres especiais vão codificados na URL:
@vira%40,:vira%3A,/vira%2F. - O
MAIL_FROMdeve ser de um domínio que autorize o seu servidor SMTP (SPF e DKIM). Sem isso, a maior parte dos e-mails cai no spam ou é recusada. Serve o SMTP do provedor do seu domínio ou um serviço de e-mail transacional; para uma escola pequena, a senha de app de uma conta Gmail também funciona, com o limite diário de envio dela.
Depois, docker compose up -d recria o backend com as variáveis novas, sem rebuild. Para testar, peça um link para a sua própria conta. Se o e-mail não chegar, procure no log do backend (docker compose logs backend) a linha falha ao enviar o link de redefinição, que traz o erro do servidor SMTP.
Sem SMTP
POST /api/v1/auth/forgot-password responde 503 e a tela não mostra o link. Quem esquece a senha não tem como recuperá-la pelo app, então vale configurar o SMTP antes de abrir a instalação para os alunos.
Login com Google
Como funciona
O login segue o OpenID Connect com authorization code, PKCE, state e nonce:
- O botão Entrar com Google leva a
/api/v1/auth/google, que grava um cookie cifrado com ostate, ononcee o segredo do PKCE e redireciona para o Google, que pergunta qual conta usar. - O Google volta para
/api/v1/auth/google/callback. A API confere ostatecontra o cookie, troca o código pelo ID token, confere a assinatura com as chaves públicas do Google, o emissor, o destinatário, a validade e ononce, e só aceita e-mail confirmado pelo Google. - A API redireciona para
/app/entrar/googlecom um código de uso único, que vale 2 minutos, e o app o troca pelo login com umPOST. O token de login nunca aparece na URL.
O OpENEM pede só os escopos openid e email: o nome e a foto da conta Google não chegam ao servidor.
Conta nova e conta que já existe
- Na primeira vez, se não há conta com aquele e-mail, o OpENEM cria uma, sem senha. O aluno pode criar uma senha depois pelo Esqueci minha senha.
- Se já há uma conta com o mesmo e-mail, as duas são vinculadas, e dali em diante o aluno entra com a senha ou com o Google.
- Se essa conta tem senha e o e-mail dela nunca foi confirmado (foi criada com senha e nunca passou por uma redefinição), a senha é desativada e as sessões abertas caem. O cadastro por senha não confirma o e-mail, então qualquer pessoa poderia ter criado a conta com o e-mail de outra e esperado o dono entrar pelo Google. O app avisa o aluno, que pode criar uma senha nova pelo Esqueci minha senha.
- Se o e-mail já está ligado a outra conta Google, ou se há duas contas com o mesmo e-mail em maiúsculas e minúsculas diferentes, o login pelo Google é recusado e o aluno entra com a senha.
Configurar no Google Cloud
-
No console do Google Cloud, crie um projeto para a instalação.
-
Abra a Google Auth Platform (no console antigo, APIs e serviços e depois Tela de consentimento OAuth). Os nomes das seções mudam de tempos em tempos; o que importa é o conteúdo:
- Informações da marca (Branding): o nome do app e o e-mail de suporte, que o aluno vê na tela do Google; a página inicial do app, que é a
SITE_URL; o link da Política de Privacidade, uma página publicada no seu domínio dizendo quais dados a instalação guarda (o e-mail, o identificador da conta Google e o que o aluno faz no app), para quê e como pedir a exclusão; e os domínios autorizados, com o domínio daSITE_URL. O Google pede o link da política e que o domínio seja verificado no Google Search Console, pela mesma conta que administra o projeto, para verificar a marca e mostrar o nome e o logo do app na tela de consentimento. - Público-alvo (Audience): tipo Externo.
- Acesso a dados (Data access): só os escopos
openide.../auth/userinfo.email. São escopos não sensíveis, que não passam pela revisão de segurança do Google.
- Informações da marca (Branding): o nome do app e o e-mail de suporte, que o aluno vê na tela do Google; a página inicial do app, que é a
-
Em Público-alvo, publique o app (Em produção). Enquanto ele está Em teste, só os usuários de teste cadastrados conseguem entrar.
-
Em Clientes (no console antigo, Credenciais), crie um ID do cliente OAuth do tipo Aplicativo da Web e cadastre o URI de redirecionamento autorizado, idêntico ao que a API usa:
https://openem.exemplo/api/v1/auth/google/callbackPara desenvolver na própria máquina, cadastre também
http://localhost:5173/api/v1/auth/google/callback. As origens JavaScript autorizadas não são usadas: a troca do código acontece no servidor. -
Copie o ID e o segredo do cliente para o
.envda raiz:SITE_URL=https://openem.exemplo GOOGLE_CLIENT_ID=1234567890-abc.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=<segredo do cliente> -
Rode
docker compose up -d. A tela de entrada passa a mostrar Entrar com Google.
O segredo do cliente fica só no servidor. Se ele vazar, gere outro no Google Cloud e troque no .env.
Quando dá errado
O app mostra uma frase para cada motivo, e a API registra no log os casos de falha:
| O aluno vê | Causa provável |
|---|---|
| Você cancelou a entrada pelo Google. | O aluno fechou ou recusou a tela do Google. |
| A entrada pelo Google demorou demais ou foi aberta em outra aba. | Passaram mais de 10 minutos, o navegador bloqueou o cookie ou a volta veio de outra aba. |
| O Google não confirmou o e-mail desta conta. | Conta Google com e-mail não confirmado. |
| Este e-mail já está ligado a outra conta Google... | Veja a última regra de Conta nova e conta que já existe. |
| Não foi possível entrar pelo Google. | URI de redirecionamento diferente do cadastrado (o Google mostra redirect_uri_mismatch), segredo errado, relógio do servidor fora da hora ou o Google fora do alcance. O motivo está no log do backend. |
Sessões
O token de login traz a versão de sessão da conta, que a API confere a cada pedido. Trocar a senha, redefini-la pelo e-mail, ter a senha desativada pelo vínculo com o Google ou tocar em Sair de todos os dispositivos sobe essa versão, e todos os tokens anteriores param de valer na hora. Com várias instâncias da API e sem Redis, cada instância guarda a versão por até 30 segundos; com o Redis do compose, a mudança vale para todas imediatamente.