Use Cases

Implementing Social Login with Claude Code

Learn about implementing social login using Claude Code. Includes practical code examples.

ソーシャルログインをClaude Codeで実装する

ソーシャルログインを導入すると、ユーザーの登録ハードルが大幅に下がります。Google、GitHub、X(旧Twitter)など複数のプロバイダーに対応するのは設定が複雑ですが、Claude Codeを使えば一貫した実装で効率的に構築できます。

NextAuth.jsによるマルチプロバイダー設定

> NextAuth.jsでソーシャルログインを実装して。
> Google、GitHub、Xの3つのプロバイダーに対応して。
> 既存アカウントとのリンク機能も含めて。
// src/lib/auth.ts
import NextAuth from 'next-auth';
import Google from 'next-auth/providers/google';
import GitHub from 'next-auth/providers/github';
import Twitter from 'next-auth/providers/twitter';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { prisma } from './prisma';

export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [
    Google({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
      authorization: {
        params: {
          prompt: 'consent',
          access_type: 'offline',
          response_type: 'code',
        },
      },
    }),
    GitHub({
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }),
    Twitter({
      clientId: process.env.TWITTER_CLIENT_ID!,
      clientSecret: process.env.TWITTER_CLIENT_SECRET!,
      version: '2.0',
    }),
  ],
  callbacks: {
    async signIn({ user, account, profile }) {
      // 既存ユーザーの場合はアカウントをリンク
      if (account && user.email) {
        const existingUser = await prisma.user.findUnique({
          where: { email: user.email },
        });
        if (existingUser) {
          await prisma.account.upsert({
            where: {
              provider_providerAccountId: {
                provider: account.provider,
                providerAccountId: account.providerAccountId,
              },
            },
            update: {},
            create: {
              userId: existingUser.id,
              provider: account.provider,
              providerAccountId: account.providerAccountId,
              type: account.type,
              access_token: account.access_token,
              refresh_token: account.refresh_token,
            },
          });
        }
      }
      return true;
    },
    async session({ session, user }) {
      session.user.id = user.id;
      return session;
    },
  },
  pages: {
    signIn: '/login',
    error: '/login?error=auth',
  },
});

ログインページのUI

// src/app/(auth)/login/page.tsx
'use client';
import { signIn } from 'next-auth/react';

const providers = [
  { id: 'google', name: 'Google', icon: '🔍', bg: 'bg-white border hover:bg-gray-50', text: 'text-gray-700' },
  { id: 'github', name: 'GitHub', icon: '🐙', bg: 'bg-gray-900 hover:bg-gray-800', text: 'text-white' },
  { id: 'twitter', name: 'X (Twitter)', icon: '𝕏', bg: 'bg-black hover:bg-gray-900', text: 'text-white' },
];

export default function LoginPage() {
  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900">
      <div className="bg-white dark:bg-gray-800 p-8 rounded-2xl shadow-lg w-full max-w-md">
        <h1 className="text-2xl font-bold text-center mb-2 dark:text-white">ログイン</h1>
        <p className="text-gray-500 text-center mb-8">アカウントでログインしてください</p>
        <div className="space-y-3">
          {providers.map((provider) => (
            <button
              key={provider.id}
              onClick={() => signIn(provider.id, { callbackUrl: '/dashboard' })}
              className={`w-full flex items-center justify-center gap-3 px-4 py-3 rounded-lg font-medium transition ${provider.bg} ${provider.text}`}
            >
              <span className="text-xl">{provider.icon}</span>
              {provider.name}でログイン
            </button>
          ))}
        </div>
        <div className="mt-6 text-center text-sm text-gray-500">
          ログインすることで
          <a href="/terms" className="text-blue-600 hover:underline">利用規約</a>と
          <a href="/privacy" className="text-blue-600 hover:underline">プライバシーポリシー</a>に同意します
        </div>
      </div>
    </div>
  );
}

アカウントリンク管理

// src/components/LinkedAccounts.tsx
'use client';
import { useState, useEffect } from 'react';

interface LinkedAccount {
  provider: string;
  providerAccountId: string;
  linkedAt: string;
}

export function LinkedAccounts() {
  const [accounts, setAccounts] = useState<LinkedAccount[]>([]);

  useEffect(() => {
    fetch('/api/user/linked-accounts')
      .then((res) => res.json())
      .then(setAccounts);
  }, []);

  const unlinkAccount = async (provider: string) => {
    if (accounts.length <= 1) {
      alert('最低1つのログイン方法が必要です');
      return;
    }
    await fetch(`/api/user/linked-accounts/${provider}`, { method: 'DELETE' });
    setAccounts(accounts.filter((a) => a.provider !== provider));
  };

  return (
    <div className="space-y-4">
      <h3 className="font-semibold dark:text-white">連携済みアカウント</h3>
      {accounts.map((account) => (
        <div key={account.provider} className="flex items-center justify-between p-3 border rounded-lg dark:border-gray-700">
          <span className="capitalize dark:text-gray-300">{account.provider}</span>
          <button
            onClick={() => unlinkAccount(account.provider)}
            className="text-red-500 text-sm hover:underline"
          >
            解除
          </button>
        </div>
      ))}
    </div>
  );
}

セキュリティのポイント

ソーシャルログインではCSRF対策とstateパラメータの検証が重要です。NextAuth.jsはこれらをデフォルトで処理しますが、カスタム実装する場合はClaude Codeに「OAuthのセキュリティベストプラクティスに沿って実装して」と指示しましょう。

関連記事

OAuth全般の実装パターンはOAuth実装ガイド、JWT認証と組み合わせる場合はJWT認証の実装も参考になります。

NextAuth.jsの公式ドキュメント(next-auth.js.org)でプロバイダーごとの詳細設定を確認できます。

#Claude Code #social login #OAuth #NextAuth.js #authentication