🚀 Oferta especial: 60% OFF no CrazyStack - Últimas vagas!Garantir vaga →
Voltar ao Curso
MÓDULO 2
AULA 5

Autenticação com Google OAuth

Implementação completa de OAuth Google com popup, tratamento de mensagens cross-origin e integração segura com cookies no Restaurantix.

45 min
OAuth 2.0
Google APIs
🌐 OAuth Google no Restaurantix

🎯 Por que OAuth ao invés de apenas Email/Senha?

  • Experiência do Usuário: Login com um clique, sem precisar lembrar senhas
  • Segurança: Google gerencia credenciais, reduzindo responsabilidade
  • Confiança: Usuários confiam em grandes provedores como Google
  • Dados Validados: Informações já verificadas pelo Google

O OAuth Google permite que usuários façam login no Restaurantix usando suas contas do Google, sem nunca compartilhar senhas com nossa aplicação. Vamos implementar o fluxo completo usando popup windows para uma experiência fluída.

🔄 Fluxo OAuth Completo

1. Popup OAuth:

Frontend abre popup direcionando para endpoint OAuth do backend

2. Google Auth:

Usuário faz login diretamente no Google (sem compartilhar senha)

3. Code Exchange:

Backend troca authorization code por access token com Google

4. User Info:

Backend busca dados do usuário na API do Google

5. JWT Cookie:

Backend cria conta/login e define cookie seguro com JWT

6. Popup Message:

Backend sinaliza sucesso via postMessage para o frontend

🛡️ Vantagens de Segurança

Sem Senhas:

Aplicação nunca vê ou armazena senhas dos usuários

OAuth 2.0:

Protocolo padrão da indústria, amplamente testado

Scopes Limitados:

Só solicitamos dados essenciais (email, nome, foto)

Revogação:

Usuário pode revogar acesso direto no Google

Cross-Origin Seguro:

Popup isolado evita vazamento de dados sensíveis

⚠️ Conceitos Importantes para Entender

OAuth 2.0:

Protocolo de autorização que permite aplicações acessarem recursos de usuários sem suas credenciais.

Authorization Code:

Código temporário que Google envia após usuário autorizar, trocado por access token.

PostMessage API:

Permite comunicação segura entre popup e janela principal de diferentes origens.

Scopes OAuth:

Permissões específicas que aplicação solicita (email, profile, etc).

✅ Vantagens desta Implementação

  • 🎨 UX Fluída: Popup não interrompe navegação principal
  • 🔒 Seguro: Cookies httpOnly + mesma arquitetura da aula anterior
  • 🔄 Unified: Mesmo fluxo para sign-in e sign-up
  • ⚡ Rápido: Login instantâneo para usuários já logados no Google
🔧 Passo 1: Configuração no Google Cloud Console

🎯 Por que Precisamos Configurar no Google?

O Google precisa saber que nossa aplicação é legítima antes de permitir OAuth. Configuramos Client ID e Client Secret que funcionam como "credenciais" da nossa aplicação no ecossistema Google.

📋 Passos no Google Cloud Console

1. Criar Projeto
  • • Acesse console.cloud.google.com
  • • Clique em "Select a project" → "New Project"
  • • Nome: "Restaurantix OAuth" ou similar
2. Habilitar Google+ API
  • • Menu → "APIs & Services" → "Library"
  • • Busque "Google+ API" → Enable
  • • Ou use "Google OAuth2 API" (mais moderno)
3. Configurar OAuth Consent Screen
  • • "APIs & Services" → "OAuth consent screen"
  • • User Type: External (permite qualquer usuário Gmail)
  • • App name: "Restaurantix"
  • • User support email: seu email
  • • Scopes: email, profile, openid (básicos)
4. Criar OAuth Client
  • • "Credentials" → "Create Credentials" → "OAuth client ID"
  • • Application type: Web application
  • • Name: "Restaurantix Web Client"
  • • Authorized redirect URIs:

🚨 URLs de Redirecionamento Críticas

Estas URLs devem estar EXATAMENTE configuradas no Google Console, ou o OAuth falhará com erro de redirect_uri_mismatch:

URLs de Redirect
// 🌍 Desenvolvimento Local
http://localhost:3000/auth/google/callback

// 🚀 Produção (substitua pelo seu domínio)
https://restaurantix.fly.dev/auth/google/callback

// 💡 Dica: URL deve terminar com /callback
// Não funciona: /auth/google
// Funciona: /auth/google/callback

🔑 Variáveis de Ambiente

Após criar o OAuth Client, você receberá Client ID eClient Secret. Configure no backend:

.env
// 📁 Backend .env
GOOGLE_CLIENT_ID=sua_client_id_aqui.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=seu_client_secret_aqui
GOOGLE_CALLBACK_URL=http://localhost:3000/auth/google/callback

// 🔍 Exemplo de Client ID real:
// 123456789-abc123def456.apps.googleusercontent.com

// ⚠️ NUNCA commite o Client Secret!
// Use .env.local ou .env (já no .gitignore)
🪟 Passo 2: Implementação do Popup OAuth

🎯 Por que Popup ao invés de Redirect Completo?

  • UX Superior: Usuário não perde contexto da página atual
  • Estado Preservado: Formulários e dados temporários não se perdem
  • Controle: Popup pode ser fechado programaticamente
  • Isolamento: OAuth ocorre em ambiente isolado

🔧 Implementação do Handler do Google

components/auth/sign-in-form.tsx
// 📁 sign-in-form.tsx (baseado no arquivo fornecido)
export function SignInForm() {
  const router = useRouter();
  const getProfile = useAuthStore((state) => state.getProfile);
  const [oauthWindow, setOauthWindow] = useState<Window | null>(null);

  // 🎯 Handler para abrir popup OAuth
  const handleGoogleSignIn = () => {
    const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';

    // 📐 Dimensões e posicionamento do popup
    const width = 500;
    const height = 600;
    const left = window.screenX + (window.outerWidth - width) / 2;
    const top = window.screenY + (window.outerHeight - height) / 2;

    // 🪟 Abre popup direcionando para endpoint OAuth do backend
    const popup = window.open(
      `${apiUrl}/auth/google`,  // 🎯 Endpoint que inicia OAuth
      'google-oauth',             // 🏷️ Nome da janela
      `width=${width},height=${height},left=${left},top=${top},popup=yes`
    );

    if (popup) {
      setOauthWindow(popup);
    } else {
      toast.error('Erro ao abrir janela de autenticação');
    }
  };

  return (
    // ... resto do formulário
    <Button
      type="button"
      variant="outline"
      className="w-full"
      onClick={handleGoogleSignIn}
      disabled={isLoading}
    >
      <svg className="mr-2 h-4 w-4" viewBox="0 0 24 24">
        {/* 🎨 Ícone oficial do Google */}
        <path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/>
        <path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/>
        <path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/>
        <path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/>
      </svg>
      Entrar com Google
    </Button>
  );
}

🧠 Entendendo os Parâmetros do Popup

width/height:

Dimensões otimizadas para tela de login do Google

left/top:

Centraliza popup na tela atual do usuário

popup=yes:

Remove barras de navegação/endereço

Nome 'google-oauth':

Identifica janela para comunicação posterior

⚡ Melhorias de UX

Melhorias de UX
// 🎨 Melhorias adicionais para experiência do usuário

const handleGoogleSignIn = () => {
  const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';

  // 🎯 Validação se popup blocker está ativo
  const popup = window.open(
    `${apiUrl}/auth/google`,
    'google-oauth',
    'width=500,height=600,left=300,top=200,popup=yes,scrollbars=yes'
  );

  if (!popup) {
    // 🚨 Popup foi bloqueado
    toast.error('Popup bloqueado! Por favor, permita popups para este site.');
    return;
  }

  // 💫 Foco no popup para melhor UX
  popup.focus();
  
  // 🔄 Monitoring: detecta se usuário fechou popup manualmente
  const checkClosed = setInterval(() => {
    if (popup.closed) {
      clearInterval(checkClosed);
      // 🛑 Usuário cancelou OAuth
      console.log('OAuth cancelado pelo usuário');
      setOauthWindow(null);
    }
  }, 1000);

  setOauthWindow(popup);
};

⚠️ Problemas Comuns e Soluções

Popup Blockers:

Navegadores bloqueiam popups que não são resultado direto de clique do usuário. Sempre chame window.open() dentro do handler do botão.

CORS Issues:

Popup carrega conteúdo do backend, que já tem CORS configurado. Problema comum é URL incorreta para o backend.

Mobile Safari:

Alguns navegadores mobile tratam popups diferente. Considere fallback para redirect completo em mobile.

📬 Passo 3: Comunicação Cross-Origin com PostMessage

🎯 Por que PostMessage API?

  • Cross-Origin: Popup e janela principal podem ter origens diferentes
  • Seguro: Verificação de origem previne ataques maliciosos
  • Assíncrono: Não bloqueia UI enquanto aguarda OAuth
  • Controlado: Backend decide quando e como notificar frontend

🎧 Listener de Mensagens no Frontend

components/auth/sign-in-form.tsx
// 📁 sign-in-form.tsx (baseado no arquivo fornecido)
export function SignInForm() {
  const router = useRouter();
  const getProfile = useAuthStore((state) => state.getProfile);
  const [oauthWindow, setOauthWindow] = useState<Window | null>(null);

  useEffect(() => {
    // 🎧 Listener para receber mensagens do popup OAuth
    const handleMessage = async (event: MessageEvent) => {
      // 🛡️ CRÍTICO: Verifica a origem da mensagem
      const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
      if (event.origin !== apiUrl) {
        console.warn('Mensagem de origem não confiável:', event.origin);
        return;
      }

      // 🔍 Verifica se é uma mensagem de autenticação
      if (event.data?.type === 'auth-complete') {
        const { success, error } = event.data.data;

        if (success) {
          // ✅ OAuth completado com sucesso
          try {
            // 👤 Os cookies já foram definidos pelo backend com httpOnly
            // Apenas fazemos a requisição para buscar o perfil atualizado
            await getProfile();
            toast.success('Login realizado com sucesso!');
            router.push('/dashboard');
          } catch {
            toast.error('Erro ao completar login');
          }
        } else {
          // ❌ Erro no OAuth
          toast.error(error || 'Erro ao fazer login com Google');
        }

        // 🚪 Fecha o popup se ainda estiver aberto
        if (oauthWindow && !oauthWindow.closed) {
          oauthWindow.close();
        }
        setOauthWindow(null);
      }
    };

    // 📡 Registra o listener
    window.addEventListener('message', handleMessage);

    // 🧹 Cleanup: remove listener quando componente desmonta
    return () => {
      window.removeEventListener('message', handleMessage);
    };
  }, [oauthWindow, router, getProfile]); // 🔄 Deps necessárias

  // ... resto do componente
}

🚨 Segurança: Verificação de Origem OBRIGATÓRIA

A verificação event.origin !== apiUrl é CRÍTICA para segurança. Sem ela, qualquer site malicioso poderia enviar mensagens falsas:

Verificação de Origem
// ❌ NUNCA faça isso - INSEGURO:
const handleMessage = async (event: MessageEvent) => {
  // Aceita mensagem de qualquer origem - VULNERÁVEL!
  if (event.data?.type === 'auth-complete') {
    // Atacante pode forjar esta mensagem
  }
};

// ✅ SEMPRE verifique origem:
const handleMessage = async (event: MessageEvent) => {
  const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
  
  // 🛡️ Proteção essencial contra ataques
  if (event.origin !== apiUrl) {
    console.warn('Origem não confiável:', event.origin);
    return; // Ignora mensagem
  }
  
  // Agora é seguro processar
  if (event.data?.type === 'auth-complete') {
    // Código de autenticação...
  }
};

🎭 Tipos de Mensagem Estruturadas

Mensagens Tipadas
// 🏗️ Interface TypeScript para mensagens OAuth
interface OAuthMessage {
  type: 'auth-complete';
  data: {
    success: boolean;
    error?: string;
    user?: {
      id: string;
      name: string;
      email: string;
      avatar?: string;
    };
  };
}

// 🎯 Handler tipado
const handleMessage = async (event: MessageEvent<OAuthMessage>) => {
  const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
  
  if (event.origin !== apiUrl) return;

  if (event.data?.type === 'auth-complete') {
    const { success, error, user } = event.data.data;
    
    if (success) {
      console.log('✅ Login bem-sucedido:', user?.name);
      await getProfile(); // Atualiza store
      router.push('/dashboard');
    } else {
      console.error('❌ Erro OAuth:', error);
      toast.error(error || 'Falha na autenticação');
    }
    
    // Limpa popup
    if (oauthWindow && !oauthWindow.closed) {
      oauthWindow.close();
    }
    setOauthWindow(null);
  }
};

🔄 Fluxo Completo de Comunicação

1. Frontend → Popup:

window.open() abre popup direcionando para /auth/google

2. Popup → Google:

Backend redireciona popup para tela de login do Google

3. Google → Popup:

Após login, Google redireciona para /auth/google/callback

4. Backend → Popup:

Backend processa OAuth e envia HTML com postMessage

5. Popup → Frontend:

PostMessage notifica janela principal sobre resultado

6. Frontend:

Fecha popup, atualiza estado e redireciona usuário

⚠️ Casos Edge e Tratamento de Erros

Popup Fechado Manualmente:

Use setInterval para detectar popup.closed e limpar estado.

Timeout:

Defina timeout de 5 minutos para auto-fechar popup em caso de problema.

Múltiplos Popups:

Disable botão OAuth enquanto popup está aberto para evitar confusão.

🔧 Passo 4: Implementação do Backend OAuth

🎯 Responsabilidades do Backend no OAuth

  • Iniciar Fluxo: Redirecionar para Google com parâmetros corretos
  • Processar Callback: Trocar authorization code por access token
  • Buscar Dados: Obter informações do usuário da API Google
  • Criar/Login: Registrar ou autenticar usuário no sistema
  • Definir Cookies: Configurar JWT cookies seguros
  • Notificar Frontend: Enviar resultado via postMessage

🚀 Endpoint de Início do OAuth

src/http/routes/auth-google.ts
// 📁 src/http/routes/auth-google.ts
import { Elysia } from 'elysia'
import { env } from '../../env'

export const authGoogle = new Elysia()
  .get('/auth/google', async ({ set }) => {
    // 🔧 Configuração dos parâmetros OAuth
    const googleAuthUrl = 'https://accounts.google.com/o/oauth2/v2/auth'
    
    // 🎯 Parâmetros necessários para OAuth Google
    const params = new URLSearchParams({
      client_id: env.GOOGLE_CLIENT_ID,
      redirect_uri: env.GOOGLE_CALLBACK_URL,
      response_type: 'code',
      scope: 'openid email profile', // 🔍 Dados que solicitamos
      access_type: 'offline',
      prompt: 'consent'
    })

    // 🌍 Redireciona para Google OAuth
    set.redirect = `${googleAuthUrl}?${params.toString()}`
  })

🔄 Endpoint de Callback

src/http/routes/auth-google-callback.ts
// 📁 src/http/routes/auth-google-callback.ts
import { Elysia } from 'elysia'
import { db } from '../../db'
import { users } from '../../db/schema'
import { eq } from 'drizzle-orm'
import { jwt } from '@elysiajs/jwt'
import { env } from '../../env'

export const authGoogleCallback = new Elysia()
  .use(jwt({ name: 'jwt', secret: env.JWT_SECRET }))
  .get('/auth/google/callback', async ({ query, set, jwt, setCookie }) => {
    try {
      const { code, error } = query

      if (error || !code) {
        return generateErrorResponse(error || 'Código não fornecido')
      }

      // 🔄 1. Trocar code por access token
      const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({
          client_id: env.GOOGLE_CLIENT_ID,
          client_secret: env.GOOGLE_CLIENT_SECRET,
          code: code as string,
          grant_type: 'authorization_code',
          redirect_uri: env.GOOGLE_CALLBACK_URL,
        }),
      })

      const tokenData = await tokenResponse.json()

      // 👤 2. Buscar dados do usuário
      const userResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
        headers: { Authorization: `Bearer ${tokenData.access_token}` },
      })

      const googleUser = await userResponse.json()

      // 🔍 3. Buscar ou criar usuário
      let user = await db.query.users.findFirst({
        where: eq(users.email, googleUser.email),
      })

      if (!user) {
        const [newUser] = await db.insert(users).values({
          email: googleUser.email,
          name: googleUser.name,
          role: 'customer',
          avatar: googleUser.picture,
          googleId: googleUser.id,
        }).returning()
        user = newUser
      }

      // 🔐 4. Gerar JWT e definir cookie
      const token = await jwt.sign({
        sub: user.id,
        email: user.email,
        role: user.role,
      })

      setCookie('auth-token', token, {
        httpOnly: true,
        secure: env.NODE_ENV === 'production',
        sameSite: env.NODE_ENV === 'production' ? 'none' : 'lax',
        maxAge: 60 * 60 * 24 * 7, // 7 dias
        path: '/',
      })

      // ✅ 5. Retornar HTML com postMessage
      return generateSuccessResponse(user)

    } catch (error) {
      console.error('Erro OAuth:', error)
      return generateErrorResponse('Erro interno')
    }
  })

🎨 Funções de Resposta HTML

Funções de Resposta
// 🎨 Função para resposta de sucesso
function generateSuccessResponse(user: any) {
  return `
    <!DOCTYPE html>
    <html>
    <head>
      <title>Login Realizado</title>
      <style>
        body { 
          font-family: system-ui; 
          display: flex; 
          align-items: center; 
          justify-content: center; 
          height: 100vh; 
          margin: 0; 
          background: #f5f5f5; 
        }
        .success { color: #22c55e; font-size: 24px; margin-bottom: 1rem; }
      </style>
    </head>
    <body>
      <div>
        <div class="success">✅ Login realizado!</div>
        <p>Redirecionando...</p>
      </div>
      <script>
        if (window.opener) {
          window.opener.postMessage({
            type: 'auth-complete',
            data: { success: true, user: ${JSON.stringify(user)} }
          }, '${env.FRONTEND_URL}');
          setTimeout(() => window.close(), 1000);
        }
      </script>
    </body>
    </html>
  `
}

// 🚨 Função para resposta de erro
function generateErrorResponse(error: string) {
  return `
    <!DOCTYPE html>
    <html>
    <head><title>Erro</title></head>
    <body>
      <div>❌ Erro: ${error}</div>
      <script>
        if (window.opener) {
          window.opener.postMessage({
            type: 'auth-complete',
            data: { success: false, error: '${error}' }
          }, '${env.FRONTEND_URL}');
          setTimeout(() => window.close(), 2000);
        }
      </script>
    </body>
    </html>
  `
}

🔗 Registrar Rotas no Servidor

src/http/server.ts
// 📁 src/http/server.ts
import { authGoogle } from './routes/auth-google'
import { authGoogleCallback } from './routes/auth-google-callback'

const app = new Elysia()
  .use(cors())
  .use(authGoogle)
  .use(authGoogleCallback)
  // ... outras rotas
  .listen(3000)

⚠️ Variáveis de Ambiente Necessárias

.env
// 📁 .env
GOOGLE_CLIENT_ID=sua_client_id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=seu_client_secret
GOOGLE_CALLBACK_URL=http://localhost:3000/auth/google/callback
FRONTEND_URL=http://localhost:3001
JWT_SECRET=seu_jwt_secret_super_seguro
🌊 Fluxo Completo OAuth em Ação

🎬 Sequência Temporal Completa

Vamos acompanhar o que acontece desde o clique no botão até o usuário estar logado:

⏰ Timeline do OAuth Google

1
Frontend: Clique no Botão

Usuário clica em "Entrar com Google" → handleGoogleSignIn() executa

Momento 1
// window.open() abre popup
const popup = window.open(
  'http://localhost:3000/auth/google',
  'google-oauth',
  'width=500,height=600,popup=yes'
)
2
Backend: Redirecionamento para Google

Rota /auth/google monta URL do Google com parâmetros OAuth

Momento 2
// Backend gera URL OAuth
const googleUrl = `https://accounts.google.com/o/oauth2/v2/auth?
client_id=${CLIENT_ID}&
redirect_uri=${CALLBACK_URL}&
response_type=code&
scope=openid email profile`

set.redirect = googleUrl
3
Google: Tela de Login

Popup mostra interface de login do Google. Usuário autoriza aplicação.

📱 "Restaurantix quer acessar sua conta Google"
📧 Email: user@gmail.com
🔐 Senha: ••••••••
✅ [Autorizar]

4
Google: Authorization Code

Google redireciona popup para nosso callback com código temporário

Momento 4
// Google redireciona para:
http://localhost:3000/auth/google/callback?code=4%2F1AX4XfWi...

// Code é temporário (10 minutos de validade)
// Usado para trocar por access token
5
Backend: Token Exchange

Callback troca authorization code por access token via POST para Google

Momento 5
// POST para https://oauth2.googleapis.com/token
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
  method: 'POST',
  body: new URLSearchParams({
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET, // 🔐 Só backend tem acesso
    code: authorizationCode,
    grant_type: 'authorization_code',
    redirect_uri: CALLBACK_URL,
  }),
})

// Retorna: { access_token, expires_in, token_type }
6
Backend: Dados do Usuário

Com access token, busca dados do usuário na API do Google

Momento 6
// GET para API do Google
const userResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
  headers: {
    Authorization: `Bearer ${access_token}`,
  },
})

// Retorna dados do usuário:
{
  "id": "1234567890",
  "email": "user@gmail.com",
  "name": "João Silva",
  "picture": "https://lh3.googleusercontent.com/...",
  "email_verified": true
}
7
Backend: Criar/Buscar Usuário

Verifica se usuário existe no banco. Se não, cria novo. Gera JWT.

Momento 7
// Busca usuário existente
let user = await db.query.users.findFirst({
  where: eq(users.email, googleUser.email),
})

if (!user) {
  // Cria novo usuário
  const [newUser] = await db.insert(users).values({
    email: googleUser.email,
    name: googleUser.name,
    avatar: googleUser.picture,
    googleId: googleUser.id,
    role: 'customer',
  }).returning()
  user = newUser
}

// Gera JWT
const token = await jwt.sign({ sub: user.id, email: user.email })
8
Backend: Cookies + PostMessage

Define cookie httpOnly e retorna HTML com script postMessage

Momento 8
// Define cookie seguro
setCookie('auth-token', jwt, {
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production',
  sameSite: 'lax',
  maxAge: 60 * 60 * 24 * 7, // 7 dias
})

// Retorna HTML com postMessage
return `<script>
  window.opener.postMessage({
    type: 'auth-complete',
    data: { success: true, user: {...} }
  }, 'http://localhost:3001');
  window.close();
</script>`
9
Frontend: Recebe Mensagem

Event listener captura postMessage, fecha popup, atualiza estado

Momento 9
// Event listener ativo
const handleMessage = async (event) => {
  if (event.origin !== 'http://localhost:3000') return
  
  if (event.data?.type === 'auth-complete') {
    const { success } = event.data.data
    
    if (success) {
      await getProfile() // Atualiza store
      toast.success('Login realizado!')
      router.push('/dashboard')
    }
    
    oauthWindow.close()
  }
}
Resultado Final

Usuário autenticado, popup fechado, redirecionado para dashboard

✅ Cookie httpOnly definido
✅ Store atualizado com dados do usuário
✅ Redirecionamento para /dashboard
✅ Toast de sucesso exibido
✅ Popup fechado automaticamente

⏱️ Tempo Total do Fluxo

Usuário Já Logado no Google:

~2-3 segundos (quase instantâneo)

Primeiro Login:

~10-15 segundos (inclui digitação)

Conexão Lenta:

~20-30 segundos (aguarda respostas)

🔍 Debug: Como Acompanhar o Fluxo

Network Tab:

Acompanhe redirects e requisições para Google APIs

Console Logs:

Adicione logs em cada etapa do backend para debugar problemas

Application Tab:

Verifique se cookies estão sendo definidos corretamente

🎉 Conclusão: OAuth Google Implementado

✅ O que Você Conquistou Nesta Aula

  • 🔧 Google Cloud Console: Configuração completa de OAuth
  • 🪟 Popup OAuth: Implementação com UX otimizada
  • 📬 PostMessage: Comunicação segura cross-origin
  • 🔄 Token Exchange: Fluxo OAuth 2.0 completo
  • 🗄️ Database Schema: Suporte a usuários OAuth
  • 🍪 Cookie Security: Mesma arquitetura da aula anterior
  • 🛡️ Origin Verification: Proteção contra ataques
  • 🎯 Error Handling: Tratamento robusto de erros

🧠 Conceitos Fundamentais Aprendidos

OAuth 2.0 Flow
  • • Authorization Code Grant
  • • Redirect URI validation
  • • Token exchange security
  • • Scope management
Frontend Architecture
  • • Window.open() popup management
  • • PostMessage API usage
  • • Cross-origin communication
  • • State management integration
Backend Security
  • • Client Secret protection
  • • HttpOnly cookie strategy
  • • User data synchronization
  • • Error response handling
UX Considerations
  • • Popup blocker handling
  • • Loading states management
  • • Mobile compatibility
  • • Error feedback to users

🚀 Próximos Passos Sugeridos

Melhorias Imediatas
  • • Implementar OAuth para Facebook, GitHub, LinkedIn
  • • Adicionar linking de contas (mesmo email, múltiplos providers)
  • • Implementar logout que revoga tokens do Google
  • • Adicionar refresh token handling para sessões longas
Segurança Avançada
  • • Implementar PKCE (Proof Key for Code Exchange)
  • • Adicionar rate limiting nas rotas OAuth
  • • Implementar detecção de tentativas de phishing
  • • Adicionar logs de auditoria para autenticações
Experiência do Usuário
  • • Fallback para redirect em dispositivos móveis
  • • Pré-carregamento de dados do usuário
  • • Onboarding personalizado para novos usuários OAuth
  • • Sincronização de avatar em tempo real

⚠️ Checklist de Produção

Configuração
  • ☐ URLs de produção no Google Console
  • ☐ Variáveis de ambiente seguras
  • ☐ HTTPS obrigatório em produção
  • ☐ Domain verification no Google
Monitoramento
  • ☐ Logs de OAuth failures
  • ☐ Métricas de conversão
  • ☐ Alertas para erro de quota
  • ☐ Dashboard de autenticações

🎯 Missão Cumprida!

Você agora domina OAuth Google com arquitetura profissional. Seus usuários podem fazer login de forma segura e fluída, mantendo a mesma proteção por cookies da aula anterior.

OAuth 2.0 Expert
Security Aware
UX Focused
Production Ready

💡 Dica Final: OAuth é uma das funcionalidades que mais impacta a conversão de usuários. Uma implementação fluída pode aumentar significativamente o número de cadastros na sua aplicação.

🔗 Integração com Aulas Anteriores

Esta implementação se integra perfeitamente com:

Aula 4 - Autenticação:

Mesma arquitetura de cookies, mesmos middlewares CSRF, mesmo store de autenticação.

Próximas Aulas:

Sistema de permissões, dashboard personalizado, e funcionalidades avançadas do Restaurantix.