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.
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.
Artigos relacionados
Workflow Obsidian para CLAUDE.md com Claude Code
Transforme notas de trabalho do Obsidian em notas operacionais CLAUDE.md para preservar contexto.
Claude Code Revenue CTA Routing: artigos para PDF, Gumroad e consultoria
Um fluxo com Claude Code para levar leitores ao PDF grátis, Gumroad ou consultoria conforme intenção.
Regras de handoff para equipes com Claude Code: evidências, permissões, rollback e receita
Formato prático para entregar trabalho do Claude Code com prova, permissões, rollback, PDF grátis, Gumroad e consultoria.