Use Cases (Atualizado: 01/06/2026)

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.

Implementando login social com Claude Code: Next.js, Auth.js, Google e GitHub OAuth

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ãoO que verificar
OAuth flowAuthorization code flow e redirect URI igual ao dashboard do provider
state e CSRFRotas padrão do Auth.js mantidas, APIs próprias validam origem
Cookie e sessãoTokens ficam no servidor; cookies consideram Secure, HttpOnly e SameSite
Account linkingEmail 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.

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.

#Claude Code #login social #OAuth #Auth.js #NextAuth.js #autenticação
Grátis

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.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.