Entrar com Google: como ligar o login à conta certa sem tomar a conta de ninguém
Use o sub do Google como chave da identidade, decida o que fazer quando o e-mail já existe, peça só os escopos de login e faça um logout que encerra a sua sessão de verdade.
A Ana tinha conta com senha no sistema de agendamento desde 2022. Hoje alguém clicou em "Entrar com Google" com um endereço igual ao dela, e o sistema, achando o e-mail, entrou direto na conta antiga, com histórico e cartão salvo. A resposta curta: o identificador da pessoa no Google é o campo sub, não o e-mail; e juntar uma conta Google a uma conta existente exige confirmação de quem já é dono dela. O botão não entrega a senha do Gmail para você e não precisa entregar mais do que nome, e-mail e esse identificador.
Este guia trata do que acontece depois que o Google responde: como modelar a conta, como decidir entre criar, entrar ou vincular, quais escopos pedir e como sair. A validação do token em si está em conferir o token do Google no servidor; aqui partimos de um token já conferido.
O que o Google realmente manda
No login com Google Identity Services, o navegador recebe um ID token, um JWT assinado pelo Google. Depois que o seu servidor confere a assinatura, o emissor e a audiência (o seu client ID), os campos que importam são:
sub: identificador estável e único da conta Google. Não muda quando a pessoa troca de e-mail.emaileemail_verified: o endereço e se o Google confirmou que a pessoa controla esse endereço.nameepicture: conforto para a tela, se o escopoprofilefoi pedido.
Nenhuma senha passa pelo seu servidor. Se um código pede a senha do Gmail num formulário seu, aquilo não é login do Google, é phishing com o seu logotipo.
Por que o e-mail não serve de chave
O e-mail muda: a pessoa sai da empresa e o endereço corporativo vai para outro funcionário, ou o domínio expira e outra pessoa registra. Se a conta é encontrada pelo e-mail, quem ganha o endereço ganha a conta. O sub não tem esse problema, porque pertence à conta Google, não à caixa de correio.
O segundo problema é a fusão silenciosa, a cena da Ana. Provar que controla um e-mail hoje não prova que foi você quem criou a conta com senha há três anos. A decisão de vincular precisa de uma prova feita dentro da conta existente.
Como modelar as contas
Separe a pessoa (usuário) das formas de entrar (identidades). Uma pessoa pode ter senha, Google e, amanhã, outro provedor.
CREATE TABLE usuarios (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
nome VARCHAR(120) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
senha_hash VARCHAR(255) NULL,
criado_em DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE identidades_externas (
provedor VARCHAR(30) NOT NULL,
sub VARCHAR(255) NOT NULL,
usuario_id BIGINT NOT NULL,
email_no_login VARCHAR(255) NOT NULL,
vinculado_em DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (provedor, sub),
FOREIGN KEY (usuario_id) REFERENCES usuarios(id)
);
A chave primária (provedor, sub) garante que uma conta Google aponte para um único usuário. O senha_hash pode ser nulo, para quem só entra pelo Google; o post login com senha protegida cobre esse campo.
A decisão no servidor, passo a passo
- Confira o ID token com a biblioteca oficial, passando o seu client ID como audiência.
- Procure a identidade por
provedoresub. Achou: é login, abra a sessão do usuário vinculado. - Não achou e
email_verifiedé falso: recuse ou peça outro método. Não crie conta com e-mail não verificado. - Não achou e o e-mail já existe em
usuarios: não entre. Peça que a pessoa entre com a senha atual e, já logada, confirme "vincular minha conta Google". - Não achou e o e-mail é novo: crie o usuário e a identidade na mesma transação.
const { OAuth2Client } = require('google-auth-library');
const CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
const google = new OAuth2Client();
async function entrarComGoogle(idToken, db) {
const ticket = await google.verifyIdToken({ idToken, audience: CLIENT_ID });
const p = ticket.getPayload();
const [vinculos] = await db.query(
'SELECT usuario_id FROM identidades_externas WHERE provedor = ? AND sub = ?',
['google', p.sub]
);
if (vinculos.length) return { tipo: 'login', usuarioId: vinculos[0].usuario_id };
if (!p.email_verified) return { tipo: 'recusado', motivo: 'email_nao_verificado' };
const [existentes] = await db.query('SELECT id FROM usuarios WHERE email = ?', [p.email]);
if (existentes.length) return { tipo: 'confirmar_vinculo', email: p.email };
const conn = await db.getConnection();
try {
await conn.beginTransaction();
const [novo] = await conn.query(
'INSERT INTO usuarios (nome, email) VALUES (?, ?)',
[p.name || p.email, p.email]
);
await conn.query(
'INSERT INTO identidades_externas (provedor, sub, usuario_id, email_no_login) VALUES (?, ?, ?, ?)',
['google', p.sub, novo.insertId, p.email]
);
await conn.commit();
return { tipo: 'cadastro', usuarioId: novo.insertId };
} catch (erro) {
await conn.rollback();
throw erro;
} finally {
conn.release();
}
}
O exemplo usa um pool do mysql2/promise. O resultado confirmar_vinculo leva a uma tela que explica: "Já existe uma conta com este e-mail. Entre com a sua senha para ligar o Google a ela." Só depois desse login a identidade é gravada, apontando para o usuário que acabou de provar a senha.
Escopos: peça só o que o login precisa
Para entrar, bastam openid, email e profile. Agenda, Drive ou contatos são outra conversa: cada escopo a mais aparece na tela de consentimento, assusta e aumenta o que você precisa proteger. Se um recurso futuro precisar da agenda, peça a permissão no momento em que a pessoa usar esse recurso. O post agenda do Google no produto mostra esse caso.
Para login, você não precisa guardar o ID token nem pedir refresh token. Confira o token, crie a sua sessão e descarte o resto. A sessão vive num cookie seu, com HttpOnly e Secure, como em cookie HttpOnly.
Sair de verdade
O logout do seu sistema apaga a sua sessão no servidor e o cookie. Ele não desloga a pessoa do Gmail, e não deve. Mas, se o botão do Google estiver com seleção automática de conta, a pessoa clica em "Sair" e entra de novo sozinha na próxima página. Na biblioteca do Google Identity Services, chame google.accounts.id.disableAutoSelect() no logout para evitar isso.
Ofereça também desvincular o Google, desde que a conta tenha outro jeito de entrar. Se a única forma é o Google, desvincular deixa a conta inacessível.
Erros comuns
- Usar o e-mail como chave da conta Google. Quem herda o endereço herda a conta.
- Vincular automaticamente ao e-mail existente. Entrega a conta antiga para quem provou só o e-mail de hoje.
- Conferir o token apenas no navegador. Qualquer um forja a resposta e entra como quiser.
- Ignorar
email_verified. Abre espaço para cadastrar contas com e-mails alheios. - Pedir escopos de agenda e Drive para um simples login. A taxa de recusa sobe e você passa a guardar acesso que não usa.
- Logout que só limpa a tela. A sessão continua válida no servidor e o próximo usuário do computador entra na conta.
Checklist
- O ID token é conferido no servidor, com o seu client ID como audiência.
- A identidade é buscada por
provedoresub. - Contas não são criadas com
email_verifiedfalso. - E-mail já existente exige login com o método atual antes do vínculo.
- Os escopos pedidos são só
openid,emaileprofile. - Nenhum token do Google fica guardado depois do login.
- O logout apaga a sessão no servidor e desativa a seleção automática.
Entre com uma conta Google, saia e entre com outra conta Google no mesmo navegador. O sistema mostrou a segunda pessoa, ou a primeira sessão continuou viva porque o seu logout não apagou nada no servidor?
Referência: https://mariovalney.com/como-colocar-o-login-do-google-no-meu-site/