Implementando login social com Claude Code: Next.js, Auth.js, Google e GitHub OAuth
Guia prático para login social seguro com Claude Code, Next.js/Auth.js, Google/GitHub OAuth, código e falhas de produção.
Login social não é apenas reduzir o formulário de cadastro. Quando usuários entram com Google ou GitHub OAuth, você também está definindo identidade, account linking, sessões, cookies, CSRF, scopes do provider, auditoria e suporte. Se você pedir ao Claude Code apenas “adicione login”, o botão pode funcionar, mas ainda podem ficar riscos: redirect URI errada, vínculo automático por email não verificado, scopes amplos demais ou client secret no código.
Este guia usa Next.js App Router, Auth.js no estilo NextAuth v5, TypeScript e Prisma Adapter. Authorization code é um código temporário trocado pelo servidor por tokens. state é um valor aleatório usado contra CSRF. Redirect URI é a URL exata para onde Google ou GitHub devolvem o navegador. Account linking é quando um usuário já autenticado conecta explicitamente outro provider ao mesmo perfil.
Defina primeiro a fronteira de segurança
A primeira decisão não é quantos providers usar, mas o que o login precisa provar. Para SaaS, treinamento, ferramenta interna ou produto para devs, Google e GitHub costumam ser suficientes. Google é bom para usuários gerais e emails corporativos. GitHub é bom para produtos técnicos. Pedir Google Drive, Calendar ou GitHub repo no primeiro login reduz confiança e conversão.
Passe tarefas pequenas para o Claude Code:
- Configurar providers do Auth.js, variáveis de ambiente e callback route.
- Criar página de login e página protegida.
- Adicionar apenas
user.idà sessão, nunca access tokens. - Permitir account linking apenas em uma página de configurações com usuário logado.
- Impedir a remoção do último método de login.
- Não gravar client secret, refresh token ou access token em código, logs, issues ou rascunhos.
Em uma validação de Masa, o maior problema não foi o código gerado, mas uma diferença entre a redirect URI no Google Cloud Console e o valor deAUTH_URL. Claude Code altera o repositório, mas não sabe automaticamente o que foi configurado em consoles externos. Por isso, URL, scope, verificação de email e regras de cookie precisam estar definidos antes.
Fluxo OAuth em diagrama
A documentação do Google recomenda authorization code flow para aplicações web. O navegador recebe umcode temporário e o servidor faz a troca com o provider. O client secret nunca deve ir para o navegador.
sequenceDiagram
participant User as User
participant App as Next.js app
participant Auth as Auth.js route
participant Provider as Google or GitHub
User->>App: Click "Continue with Google"
App->>Auth: signIn("google")
Auth->>Provider: Redirect with client_id, redirect_uri, scope, state
Provider->>User: Consent screen
Provider->>Auth: Redirect back with code and state
Auth->>Provider: Exchange code on the server
Provider->>Auth: Return tokens
Auth->>App: Create session cookie
App->>User: Show dashboard
state confirma que o callback pertence à tentativa de login iniciada pela aplicação. Auth.js cuida disso nas rotas padrão. Se Claude Code criar callback próprio, exija validação de state. A documentação do GitHub também recomenda um valor imprevisível e encerrar o fluxo se ele não bater.
Variáveis de ambiente e scopes mínimos
Esta configuração serve para login, não para pedir permissões desnecessárias de APIs. Google usaopenid email profile; GitHub usaread:user user:email.
npm install next-auth@beta @auth/prisma-adapter prisma @prisma/client
npx prisma init
npm exec auth secret
# .env.local
AUTH_SECRET="valor gerado por npm exec auth secret"
AUTH_URL="http://localhost:3000"
AUTH_GOOGLE_ID="client ID do Google Cloud Console"
AUTH_GOOGLE_SECRET="client secret do Google Cloud Console"
AUTH_GITHUB_ID="client ID do GitHub OAuth App"
AUTH_GITHUB_SECRET="client secret do GitHub OAuth App"
DATABASE_URL="postgresql://user:password@localhost:5432/app"
O arquivo de exemplo deve conter apenas chaves vazias.
# .env.example
AUTH_SECRET=
AUTH_URL=
AUTH_GOOGLE_ID=
AUTH_GOOGLE_SECRET=
AUTH_GITHUB_ID=
AUTH_GITHUB_SECRET=
DATABASE_URL=
No Google, a redirect URI local éhttp://localhost:3000/api/auth/callback/google; em produção,https://example.com/api/auth/callback/google. No GitHub, usehttps://example.com/api/auth/callback/github. Protocolo, domínio, caminho e provider precisam ser idênticos.
Configuração do Auth.js
O arquivo abaixo rejeita Google quandoemail_verified não é verdadeiro e rejeita GitHub quando nenhum email é retornado. Tokens do provider não entram na sessão do navegador.
// auth.ts
import NextAuth, { type NextAuthConfig } from "next-auth";
import Google from "next-auth/providers/google";
import GitHub from "next-auth/providers/github";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "@/lib/prisma";
type GoogleProfile = {
sub: string;
name?: string;
email: string;
email_verified: boolean;
picture?: string;
};
export const authConfig = {
adapter: PrismaAdapter(prisma),
session: { strategy: "database" },
providers: [
Google({
authorization: {
params: {
scope: "openid email profile",
response_type: "code",
},
},
profile(profile: GoogleProfile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture,
emailVerified: profile.email_verified ? new Date() : null,
};
},
}),
GitHub({
authorization: {
params: {
scope: "read:user user:email",
},
},
}),
],
callbacks: {
async signIn({ account, profile, user }) {
if (account?.provider === "google") {
const googleProfile = profile as GoogleProfile | undefined;
return Boolean(googleProfile?.email && googleProfile.email_verified);
}
if (account?.provider === "github") {
return Boolean(user.email);
}
return true;
},
async session({ session, user }) {
session.user.id = user.id;
return session;
},
},
pages: {
signIn: "/login",
error: "/login",
},
} satisfies NextAuthConfig;
export const { handlers, auth, signIn, signOut } = NextAuth(authConfig);
// src/types/next-auth.d.ts
import "next-auth";
declare module "next-auth" {
interface Session {
user: {
id: string;
name?: string | null;
email?: string | null;
image?: string | null;
};
}
}
// src/app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth";
export const { GET, POST } = handlers;
Com Prisma Adapter, peça ao Claude Code para adicionar também os modelosUser, Account, Session eVerificationToken. A tabelaAccount é a base para auditar quais providers estão ligados ao usuário.
Página de login e página protegida
ChamarsignIn em uma Server Action mantém o fluxo padrão do Auth.js e evita montar URLs OAuth manualmente.
// src/app/login/page.tsx
import { signIn } from "@/auth";
const providers = [
{ id: "google", label: "Continuar com Google" },
{ id: "github", label: "Continuar com GitHub" },
] as const;
export default function LoginPage({
searchParams,
}: {
searchParams: { error?: string };
}) {
return (
<main className="mx-auto flex min-h-screen max-w-sm flex-col justify-center gap-6 px-6">
<div>
<h1 className="text-2xl font-bold">Entrar</h1>
<p className="mt-2 text-sm text-gray-600">
Escolha a conta de trabalho que será usada nesta aplicação.
</p>
</div>
{searchParams.error ? (
<p className="rounded-md bg-red-50 p-3 text-sm text-red-700">
Não foi possível entrar. Tente outra conta ou fale com o suporte.
</p>
) : null}
<div className="grid gap-3">
{providers.map((provider) => (
<form
key={provider.id}
action={async () => {
"use server";
await signIn(provider.id, { redirectTo: "/dashboard" });
}}
>
<button
type="submit"
className="w-full rounded-md border px-4 py-3 text-sm font-medium hover:bg-gray-50"
>
{provider.label}
</button>
</form>
))}
</div>
</main>
);
}
// src/app/dashboard/page.tsx
import { auth } from "@/auth";
import { redirect } from "next/navigation";
export default async function DashboardPage() {
const session = await auth();
if (!session?.user) {
redirect("/login");
}
return (
<main className="mx-auto max-w-3xl p-8">
<h1 className="text-2xl font-bold">Dashboard</h1>
<p className="mt-4 text-gray-700">
Signed in as {session.user.email}.
</p>
</main>
);
}
Account linking e remoção segura
Não conecte contas automaticamente só porque o email é igual. O usuário deve estar logado e iniciar a conexão nas configurações. A API de remoção valida a origem e impede apagar o último método de login.
// src/app/api/settings/linked-accounts/route.ts
import { auth } from "@/auth";
import { prisma } from "@/lib/prisma";
import { NextResponse } from "next/server";
function isSameOrigin(request: Request) {
const origin = request.headers.get("origin");
const host = request.headers.get("host");
if (!origin || !host) return false;
try {
return new URL(origin).host === host;
} catch {
return false;
}
}
export async function DELETE(request: Request) {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
if (!isSameOrigin(request)) {
return NextResponse.json({ error: "Bad origin" }, { status: 403 });
}
const body = (await request.json()) as { provider?: string };
const accounts = await prisma.account.findMany({
where: { userId: session.user.id },
select: { provider: true },
});
if (accounts.length <= 1) {
return NextResponse.json(
{ error: "Você não pode remover o último método de login." },
{ status: 400 },
);
}
await prisma.account.deleteMany({
where: { userId: session.user.id, provider: body.provider },
});
return NextResponse.json({ ok: true });
}
Prompt para Claude Code e revisão
Adicione login social Google/GitHub a esta aplicação Next.js App Router usando Auth.js v5.
Requisitos:
- Ler client secrets apenas de .env.local ou secrets do deploy
- Google scope deve ser openid email profile
- GitHub scope deve ser read:user user:email
- Permitir Google somente se email_verified for true
- Adicionar apenas user.id à sessão e nunca expor access_token ao cliente
- Account linking apenas em página de configurações com usuário logado
- Não permitir remover o último método de login
- Ao final, reportar lint e passos manuais de teste OAuth
| Revisão | O que verificar |
|---|---|
| OAuth flow | Authorization code flow e redirect URI igual ao dashboard do provider |
| state e CSRF | Rotas padrão do Auth.js mantidas, APIs próprias validam origem |
| Cookie e sessão | Tokens ficam no servidor; cookies consideram Secure, HttpOnly e SameSite |
| Account linking | Email verificado e ação explícita antes de conectar contas |
Casos de uso reais
Primeiro, um painel B2B SaaS pode usar Google como login principal e adicionar domínio Workspace e RBAC depois. GitHub pode ficar restrito a usuários técnicos.
Segundo, uma ferramenta para devs pode usar GitHub no onboarding e pedir permissões de repositório somente dentro da funcionalidade que precisa delas.
Terceiro, um produto com email e senha pode adicionar social login fazendo o usuário logado vincular Google ou GitHub nas configurações, evitando fusões acidentais.
Quarto, uma página de treinamento ou webinar pode reduzir atrito no cadastro e usar email verificado do Google para suporte e presença.
Falhas comuns em produção
Redirect URI errada é o erro mais comum. Se funciona localmente e falha em produção, compareAUTH_URL, headers Host do proxy e callback URL no Google/GitHub.
Vinculação automática por email é arriscada. Google forneceemail_verified, mas nem todo provider dá a mesma garantia. O mesmo texto de email não prova a mesma pessoa.
Scopes excessivos reduzem PV e consultas. Usuários que só querem entrar tendem a abandonar se virem pedido de acesso a repositórios ou arquivos.
Client secret no código deve bloquear o review. Use.env.local, secrets do deploy ou secret manager.
Refresh token do Google precisa de desenho próprio. Google pode retorná-lo apenas no primeiro consentimento. Para login puro, não salve; para APIs em background, planeje rotação de token separadamente.
Referências oficiais e links internos
Verifique fontes primárias: Auth.js, Google Identity Services OAuth, GitHub OAuth Apps e OWASP Authentication Cheat Sheet.
Para aprofundar, leia implementação OAuth com Claude Code, autenticação JWT, boas práticas de segurança e gestão de variáveis de ambiente.
Consultoria, treinamento e pontos de verificação
ClaudeCodeLab ajuda a transformar um botão de login em um fluxo de autenticação revisável: requisitos, tarefas para Claude Code, review, gestão de secrets e testes manuais OAuth. Se a meta é aumentar consultas, comece definindo público, provider, permissões mínimas e mensagens de erro.
Ao testar este artigo, confirme que as redirect URIs de desenvolvimento e produção são idênticas, que Googleemail_verified é verificado, que GitHub sem email público falha com mensagem clara, que state e cookies seguem o fluxo padrão do Auth.js, que o último método de login não pode ser removido e que nenhum client secret aparece no código ou logs.
Artigos relacionados
Autenticação segura com Claude Code: sessões Next.js, JWT e OAuth
Implemente autenticação segura com Claude Code: sessões Next.js, JWT, OAuth, CSRF, RBAC, auditoria e testes.
Autenticação JWT com Claude Code: claims, cookies, rotação e chaves
Implemente JWT com Claude Code: claims, cookies, rotação de refresh tokens, revogação, chaves e prompts seguros.
Criar um Discord Bot com Claude Code: Slash Commands com discord.js
Crie um Discord Bot seguro com Claude Code, discord.js e três Slash Commands úteis para suporte.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.