Autenticação Híbrida: JWT Tradicional e OAuth2 com Google
Veja como estruturar um sistema de autenticação sólido que suporte tanto o clássico login com E-mail e Senha, via JWT, quanto o Single Sign-On (SSO) através do Google.
Em aplicativos modernos, fornecer apenas o login por email e senha não é suficiente. Os usuários querem a conveniência de fazer login com suas contas do Google, Apple ou GitHub. Mas como unificar esses dois mundos na sua base de dados?
O Modelo de Usuário
O segredo para uma autenticação flexível está na modelagem do banco. Se você exige que o campo password seja obrigatório no banco de dados, terá problemas ao registrar usuários via OAuth (pois eles não fornecem senha).
A melhor abordagem é tornar o password nulo/opcional e, idealmente, ter um campo de identificador do provedor (ex: googleId).
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@Column({ unique: true })
email: string;
@Column({ nullable: true })
password?: string; // Opcional
@Column({ nullable: true })
googleId?: string; // Opcional
}Autenticação Tradicional (JWT)
Na autenticação tradicional, o usuário envia e-mail e senha. Verificamos o hash usando bcrypt e geramos um JSON Web Token (jsonwebtoken).
import { compare } from 'bcrypt';
import { sign } from 'jsonwebtoken';
export class AuthenticateUserService {
public async execute({ email, password }: Record<string, string>) {
const user = await usersRepository.findByEmail(email);
if (!user || !user.password) throw new AppError('Incorrect credentials');
const passwordMatch = await compare(password, user.password);
if (!passwordMatch) throw new AppError('Incorrect credentials');
const token = sign({}, process.env.JWT_SECRET as string, {
subject: user.id,
expiresIn: '1d'
});
return { user, token };
}
}Autenticação via Google (OAuth2)
O fluxo OAuth2 é diferente. O frontend redireciona o usuário para a página de consentimento do Google. O Google então redireciona de volta para sua API trazendo os dados do perfil.
A biblioteca passport-google-oauth20 e google-auth-library facilitam esse trabalho. Ao receber o retorno do Google, nossa lógica deve:
- Verificar se já existe um usuário com aquele
googleId. - Se não existir pelo
googleId, verificar peloemail(vincular a conta existente). - Se não existir por nenhum dos dois, criar um novo usuário sem senha.
- Gerar e retornar um JWT idêntico ao da autenticação tradicional.
O grande pulo do gato: a saída de ambos os fluxos é a mesma (um token JWT). Dessa forma, seus middlewares de proteção de rota (ensureAuthenticated) só precisam saber validar um JWT. Eles não se importam se o usuário logou com senha ou pelo Google.
Conclusão
Manter os métodos de autenticação normalizados através de um JWT padronizado garante escalabilidade e segurança. Se amanhã você quiser adicionar login com GitHub, sua aplicação já está preparada e seus middlewares de proteção de rota continuarão intactos.