あなたは今、こんな状況ではありませんか?

「AIにコードを書いてもらったけど、動かない...」
「エラーメッセージの意味が全くわからない...」
「そもそも何をしているのか理解できない...」

安心してください。この記事を読めば、30分後には「Xでログイン」ボタンが動きます。

この第1部のゴール

「X (Twitter) でサインイン」ボタンを押すと、自分のTwitterアカウントで認証できる

たったこれだけです。しかし、認証さえできれば、第2部・第3部でツイートの表示や投稿が簡単になります。

まずは、この「ログイン機能」を完成させましょう。


3部作で何ができるようになるのか

このシリーズを完走すると、以下のアプリが完成します:

  • ✅ X(Twitter)アカウントでログイン
  • ✅ 自分のツイートを表示
  • ✅ 新しいツイートを投稿
  • ✅ ツイートを削除

第1部(今回): ログイン機能
第2部: ツイート表示機能
第3部: ツイート投稿・削除機能


目次

  1. そもそもTwitter APIって何?
  2. OAuth認証とは?
  3. プロジェクトのセットアップ
  4. Twitter Developer Portalでアプリを登録
  5. 認証システムの実装
  6. 動作確認
  7. よくあるエラーと解決方法

1. そもそもTwitter APIって何?

1-1. APIとは「郵便配達システム」

API(Application Programming Interface)とは、プログラム同士がやり取りするための窓口です。

例え話をしましょう。

あなたが手紙を送りたいとき、郵便局に行きますね。郵便局は以下のルールを決めています:

  • 住所を正しく書く
  • 切手を貼る
  • ポストに投函する

これらのルールに従えば、郵便局が手紙を届けてくれます。

Twitter APIも同じです。

  • 「このデータが欲しい」とリクエストを送る
  • Twitter側が決めたルールに従う
  • Twitterがデータを返してくれる

1-2. Twitter APIでできること

Twitter APIを使うと、以下のようなことができます:

できること説明
ツイート取得自分や他人のツイートを取得
ツイート投稿プログラムから自動投稿
ツイート削除古いツイートを一括削除
ユーザー情報取得フォロワー数などを取得
タイムライン取得最新のツイートを取得

ただし、無料プランには制限があります

  • ツイート投稿:月50回
  • ツイート読み取り:月10,000回

趣味で使う分には十分な量です。

1-3. なぜTwitter APIを使うのか?

普通にTwitterアプリを使えばいいのでは?と思うかもしれません。

しかし、Twitter APIを使えば:

  • 自分だけのカスタムクライアントが作れる
  • 自動投稿Botが作れる
  • データ分析ができる
  • 複数アカウント管理が楽になる

今回は、自分の投稿だけに集中できるシンプルなクライアントを作ります。


2. OAuth認証とは?

2-1. なぜパスワードを渡さないのか

Twitterにログインするには、通常はメールアドレスとパスワードを入力しますね。

しかし、私たちが作るアプリに、Twitterのパスワードを入力してもらうのは危険です。なぜなら:

❌ パスワードが漏洩する可能性がある
❌ アプリがパスワードを悪用するかもしれない
❌ パスワードを変更したら、アプリが使えなくなる

そこで登場するのがOAuth(オーオース)認証です。

2-2. OAuth認証の仕組み:「入館証」のイメージ

OAuth認証は、**「入館証を借りる」**システムに似ています。

通常のログイン(パスワード方式):

あなた → アプリ → Twitter
      パスワード渡す

これは危険です。アプリにパスワードを知られてしまいます。

OAuth認証:

1. あなた → Twitter(公式サイト): パスワード入力
2. Twitter → アプリ: 「入館証(アクセストークン)」を発行
3. アプリ → Twitter API: 入館証を見せてデータを取得

アプリはパスワードを知らないまま、Twitterのデータにアクセスできます。

2-3. アクセストークンとは

アクセストークンは、以下のような特徴があります:

特徴説明
通行証パスワードの代わりに使える
有効期限あり期限が切れたら再取得が必要
いつでも無効化不正使用されたら即座に無効化できる
権限限定「読み取りだけ」「投稿もOK」など選べる

このトークンがあれば、パスワードを渡さなくても、Twitterのデータにアクセスできます。


3. プロジェクトのセットアップ

3-1. 必要な環境

まず、以下がインストールされているか確認してください:

  • Node.js(バージョン18以上)
  • npm(Node.jsに付属)

確認方法:

node --version
npm --version

バージョン番号が表示されればOKです。

インストールしていない場合:

Node.js公式サイトからインストールしてください。

3-2. Next.jsプロジェクトを作る

ターミナル(コマンドプロンプト)を開いて、以下を実行します:

npx create-next-app@latest x-my-posts

いくつか質問されるので、以下のように選択してください:

✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like your code inside a `src/` directory? … No
✔ Would you like to use App Router? … Yes
✔ Would you like to use Turbopack for next dev? … Yes
✔ Would you like to customize the import alias (@/* by default)? … No

なぜこの選択?

選択項目理由
TypeScriptコードのミスを見つけやすくする
Tailwind CSSデザインを簡単に書ける
App RouterNext.js 13以降の新しい機能

作成が完了したら、プロジェクトフォルダに移動します:

cd x-my-posts

3-3. NextAuth.jsをインストール

NextAuth.jsは、OAuth認証を簡単に実装できるライブラリです。

インストール:

npm install next-auth

なぜNextAuth.jsを使うのか?

OAuth認証は複雑で、セキュリティ上の注意点も多いです。NextAuth.jsを使えば:

✅ 安全な認証フローを簡単に実装できる
✅ セッション管理が自動化される
✅ Twitter、Google、GitHubなど、複数の認証に対応できる

自分で全部実装するのは大変なので、このライブラリに任せます。

3-4. プロジェクトの構造を理解する

プロジェクトフォルダを開くと、こんな構造になっています:

x-my-posts/
├── app/              ← ページやAPIを作る場所
├── public/           ← 画像などを置く場所
├── node_modules/     ← インストールしたライブラリ
├── package.json      ← プロジェクトの設定ファイル
└── ...

この第1部では、app/フォルダの中に以下を作成します:

app/
├── api/
│   └── auth/
│       └── [...nextauth]/
│           └── route.ts    ← NextAuth.jsのエンドポイント
├── SessionProvider.tsx     ← セッション管理
├── layout.tsx              ← 全ページ共通のレイアウト
└── page.tsx                ← トップページ

さらに、lib/フォルダを作って、設定ファイルを置きます:

lib/
└── auth.ts                 ← NextAuth.jsの設定

4. Twitter Developer Portalでアプリを登録

4-1. Twitter Developer Accountを作る

Twitterのアカウントを持っていることが前提です。

手順:

  1. Twitter Developer Portalにアクセス
  2. Twitterアカウントでログイン
  3. 初めての場合、開発者アカウントの申請が必要

申請時に聞かれる質問の例:

質問回答例
何を作りますか?Personal Twitter client app
どう使いますか?To manage my own tweets

英語で答える必要がありますが、Google翻訳を使えば大丈夫です。

4-2. 新しいアプリを作成

手順:

  1. Developer Portalで**「Create App」または「+ Create Project」**をクリック
  2. プロジェクト名を入力(例:My Twitter App Project
  3. 使用目的を選択(例:Making a bot
  4. アプリ名を入力(例:X My Posts

完了すると、2つの重要な情報が表示されます:

  • API Key(Client ID)
  • API Key Secret(Client Secret)

⚠️ これは絶対に他人に見せないでください! パスワードと同じくらい大切です。

メモ帳にコピーしておきましょう。

4-3. OAuth 2.0の設定(超重要!)

次に、**「Settings」タブに移動して、「User authentication settings」**の「Edit」または「Set up」をクリックします。

以下のように設定してください:

App permissions(アプリの権限)

Read and Write

なぜ「Read and Write」?

  • Read:ツイートを読み取る
  • Write:ツイートを投稿する

「Read Only」のままだと、ツイート投稿ができません。これを忘れると、後でエラーになります。

Type of App(アプリの種類)

Web App, Automated App or Bot

App info(アプリ情報)

以下を入力します:

Callback URI / Redirect URL(⚠️ 超重要!)

http://localhost:3000/api/auth/callback/twitter

これは何?

OAuth認証が成功した後、Twitterがユーザーを「どこに戻すか」を指定します。

絶対に間違えないでください。 1文字でも違うと、認証が失敗します。

Website URL

https://your-domain.com

開発中は適当なURLでOKです。本番環境では、実際のURLに変更します。

すべて入力したら、**「Save」**をクリックします。

4-4. 環境変数を設定する

プロジェクトのルートフォルダ(x-my-posts/)に、.env.localという名前のファイルを作ります。

Macの場合:

touch .env.local

Windowsの場合:

エクスプローラーで右クリック → 新規作成 → テキストドキュメント → 名前を.env.localに変更

ファイルの中身:

# Twitter API Credentials
TWITTER_CLIENT_ID=あなたのClient ID
TWITTER_CLIENT_SECRET=あなたのClient Secret

# NextAuth.js Configuration
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=ランダムな文字列

NEXTAUTH_SECRETの生成方法:

ターミナルで以下を実行:

openssl rand -base64 32

出力された文字列をコピーして、NEXTAUTH_SECRETに貼り付けます。

Windowsでopensslがない場合:

ランダム文字列生成サイトで32文字のランダム文字列を生成してください。

なぜ環境変数を使うのか?

✅ セキュリティ:APIキーをコードに直接書かない
✅ 柔軟性:開発環境と本番環境で異なる値を使える
.env.localは自動的にGitに含まれないので、誤って公開する心配がない


5. 認証システムの実装

ここからコードを書いていきます。全て手順通りに進めれば、必ず動きます。

5-1. NextAuth.jsの型定義を拡張する

まず、types/フォルダを作ります:

mkdir types

次に、types/next-auth.d.tsファイルを作成します。

なぜこのファイルが必要?

NextAuth.jsは、デフォルトではアクセストークンをセッションに含めません。しかし、Twitter APIを呼び出すには、このトークンが必要です。そこで、NextAuth.jsの型定義を拡張して、アクセストークンを追加します。

types/next-auth.d.ts:

import "next-auth";
import "next-auth/jwt";

declare module "next-auth" {
    interface Session {
        accessToken?: string;
    }
}

declare module "next-auth/jwt" {
    interface JWT {
        accessToken?: string;
        refreshToken?: string;
    }
}

これは何をしているのか?

TypeScriptに「セッションとJWTにaccessTokenというフィールドを追加するよ」と教えています。

詳しくは今は気にしなくて大丈夫です。「アクセストークンを保存するための準備」と理解してください。

5-2. NextAuth.jsの設定ファイルを作る

lib/フォルダを作ります:

mkdir lib

次に、lib/auth.tsファイルを作成します。

lib/auth.ts:

import { NextAuthOptions } from "next-auth";
import TwitterProvider from "next-auth/providers/twitter";

export const authOptions: NextAuthOptions = {
    providers: [
        TwitterProvider({
            clientId: process.env.TWITTER_CLIENT_ID!,
            clientSecret: process.env.TWITTER_CLIENT_SECRET!,
            version: "2.0", // Twitter API v2を使用
            authorization: {
                params: {
                    scope: "users.read tweet.read tweet.write offline.access",
                },
            },
        }),
    ],
    session: {
        strategy: "jwt",
    },
    callbacks: {
        async jwt({ token, account }) {
            // 初回ログイン時にアクセストークンを保存
            if (account) {
                token.accessToken = account.access_token as string;
                token.refreshToken = account.refresh_token as string;
            }
            return token;
        },
        async session({ session, token }) {
            // セッションにアクセストークンを追加
            if (token.accessToken) {
                session.accessToken = token.accessToken as string;
            }
            return session;
        },
    },
    debug: process.env.NODE_ENV === "development",
};

このコードを分解して説明します。

providers(認証プロバイダー)

providers: [
    TwitterProvider({
        clientId: process.env.TWITTER_CLIENT_ID!,
        clientSecret: process.env.TWITTER_CLIENT_SECRET!,
        version: "2.0",
        authorization: {
            params: {
                scope: "users.read tweet.read tweet.write offline.access",
            },
        },
    }),
]

各行の意味:

コード意味
TwitterProviderTwitterでログインできるようにする
clientId.env.localから読み込む
clientSecret.env.localから読み込む
version: "2.0"Twitter API v2を使用
scope何ができるかを指定

scopeの意味:

スコープ意味
users.readユーザー情報を読む
tweet.readツイートを読む
tweet.writeツイートを投稿する(重要!)
offline.accessアクセストークンを長期間使える

⚠️ tweet.writeがないと、第3部でツイート投稿ができません!

session(セッション戦略)

session: {
    strategy: "jwt",
}

**JWT(ジェイダブリューティー)**とは、認証情報を安全に保存する方法です。

詳しい仕組みは今は気にしなくて大丈夫です。「認証情報を暗号化して保存している」とだけ理解してください。

callbacks(コールバック関数)

callbacks: {
    async jwt({ token, account }) {
        if (account) {
            token.accessToken = account.access_token as string;
            token.refreshToken = account.refresh_token as string;
        }
        return token;
    },
    async session({ session, token }) {
        if (token.accessToken) {
            session.accessToken = token.accessToken as string;
        }
        return session;
    },
}

これは何をしているのか?

  1. jwtコールバック:ログイン成功時、Twitterからもらったアクセストークンを保存する
  2. sessionコールバック:保存したトークンをセッションに追加する

こうすることで、後でTwitter APIを呼び出すときに、このトークンを使えるようになります。

5-3. NextAuth.js APIルートを作る

app/api/auth/[...nextauth]/フォルダを作ります:

mkdir -p app/api/auth/\[...nextauth\]

Windowsの場合:

エクスプローラーで手動でフォルダを作成してください:

app → api → auth → [...nextauth]

[...nextauth]とは?

Next.jsのキャッチオールルートです。/api/auth/以下のすべてのパスをこのファイルで処理します。

例:

  • /api/auth/signin → ログインページ
  • /api/auth/callback/twitter → 認証後のコールバック
  • /api/auth/signout → ログアウト

NextAuth.jsがこれらのパスを自動的に処理してくれます。

app/api/auth/[...nextauth]/route.ts:

import NextAuth from "next-auth";
import { authOptions } from "@/lib/auth";

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };

これは何をしているのか?

  • NextAuth(authOptions):先ほど作った設定を使って、NextAuth.jsを初期化
  • export { handler as GET, handler as POST }:GETリクエストとPOSTリクエストの両方を処理

これで、認証に必要なAPIが自動的に作られました。

5-4. SessionProviderを作る

app/SessionProvider.tsxを作成します:

"use client";

import { SessionProvider as NextAuthSessionProvider } from "next-auth/react";

export default function SessionProvider({
    children,
    session,
}: {
    children: React.ReactNode;
    session: any;
}) {
    return (
        <NextAuthSessionProvider session={session}>
            {children}
        </NextAuthSessionProvider>
    );
}

なぜ別ファイルにするのか?

Next.js App Routerでは、layout.tsxはサーバーコンポーネントです。しかし、SessionProviderはクライアントコンポーネント("use client"が必要)なので、別ファイルに分けます。

5-5. レイアウトを編集する

app/layout.tsxを以下のように編集します:

import "./globals.css";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import SessionProvider from "./SessionProvider";

export const metadata = {
    title: "X My Posts",
    description: "自分の投稿に集中できるシンプルなXクライアント",
};

export default async function RootLayout({
    children,
}: {
    children: React.ReactNode;
}) {
    const session = await getServerSession(authOptions);

    return (
        <html lang="ja">
            <body>
                <SessionProvider session={session}>
                    {children}
                </SessionProvider>
            </body>
        </html>
    );
}

これは何をしているのか?

SessionProviderでアプリ全体を囲むことで、どのページでもセッション情報(ログイン状態)にアクセスできるようになります。

5-6. トップページを作る

app/page.tsxを以下のように編集します:

"use client";

import { useSession, signIn, signOut } from "next-auth/react";

export default function Home() {
    const { data: session, status } = useSession();

    // ローディング中
    if (status === "loading") {
        return (
            <div className="flex min-h-screen items-center justify-center">
                <p className="text-gray-500">読み込み中...</p>
            </div>
        );
    }

    // ログインしていない場合
    if (!session) {
        return (
            <div className="flex min-h-screen flex-col items-center justify-center bg-gray-50">
                <div className="space-y-6 text-center">
                    <div className="space-y-2">
                        <h1 className="text-4xl font-bold text-gray-900">X My Posts</h1>
                        <p className="text-gray-600">
                            自分の投稿に集中できるシンプルなXクライアント
                        </p>
                    </div>
                    <button
                        onClick={() => signIn("twitter")}
                        className="px-6 py-3 bg-blue-500 text-white font-medium rounded-full hover:bg-blue-600 transition-colors"
                    >
                        X (Twitter) でサインイン
                    </button>
                </div>
            </div>
        );
    }

    // ログイン済みの場合
    return (
        <div className="min-h-screen bg-gray-50">
            <header className="bg-white border-b border-gray-200 sticky top-0 z-10">
                <div className="max-w-2xl mx-auto px-4 py-4 flex items-center justify-between">
                    <h1 className="text-xl font-bold text-gray-900">X My Posts</h1>
                    <div className="flex items-center gap-4">
                        <span className="text-sm text-gray-600">
                            {session.user?.name || "ユーザー"}
                        </span>
                        <button
                            onClick={() => signOut()}
                            className="text-sm text-gray-600 hover:text-gray-900"
                        >
                            サインアウト
                        </button>
                    </div>
                </div>
            </header>

            <main className="max-w-2xl mx-auto px-4 py-6">
                <div className="bg-white border border-gray-200 rounded-lg p-6">
                    <h2 className="text-lg font-semibold mb-2">🎉 認証成功!</h2>
                    <p className="text-gray-600">
                        ログインできました。第2部では、ツイートを表示する機能を実装します。
                    </p>
                </div>
            </main>
        </div>
    );
}

コードの説明:

useSessionフック

const { data: session, status } = useSession();
  • session:ログイン情報(ユーザー名など)
  • status:ログイン状態
    • loading:読み込み中
    • authenticated:ログイン済み
    • unauthenticated:未ログイン

状態に応じた表示

  1. ローディング中:「読み込み中...」を表示
  2. 未ログイン:「Xでサインイン」ボタンを表示
  3. ログイン済み:ユーザー名と「サインアウト」ボタンを表示

signIn関数

onClick={() => signIn("twitter")}

この関数を呼ぶと、Twitter認証ページにリダイレクトされます。


6. 動作確認

6-1. 開発サーバーを起動

ターミナルで以下を実行します:

npm run dev

以下のようなメッセージが表示されればOKです:

  ▲ Next.js 15.0.3
  - Local:        http://localhost:3000
  - Environments: .env.local

 ✓ Starting...
 ✓ Ready in 2.5s

ブラウザでhttp://localhost:3000を開きます。

6-2. ログインしてみる

手順:

  1. 「X (Twitter) でサインイン」ボタンをクリック
  2. Twitter認証ページにリダイレクトされる
  3. 「アプリを認証」をクリック
  4. アプリに戻ってくる
  5. 「🎉 認証成功!」というメッセージが表示される

成功です!

6-3. ログを確認する

ターミナルに以下のようなログが表示されていれば、認証が成功しています:

[auth][debug] session callback
[auth][debug] token: {
  accessToken: "...",
  ...
}

これで、第1部は完了です。


7. よくあるエラーと解決方法

エラー1: 「Callback URL Mismatch」

症状:

認証後に「Callback URL Mismatch」というエラーが表示される。

原因:

Twitter Developer Portalで設定したCallback URLと、実際のURLが一致していない。

解決方法:

  1. Developer Portalで「User authentication settings」を確認
  2. Callback URLが以下になっているか確認:
   http://localhost:3000/api/auth/callback/twitter
  1. 1文字でも違うとエラーになるので、コピペを推奨

エラー2: 「Invalid Client ID or Secret」

症状:

認証ページにリダイレクトされず、エラーが表示される。

原因:

.env.localのClient IDまたはClient Secretが間違っている。

解決方法:

  1. Twitter Developer Portalで「Keys and tokens」タブを開く
  2. Client IDとClient Secretを再確認
  3. .env.localにコピペし直す
  4. 開発サーバーを再起動する(重要!)
   # Ctrl + C で停止
   npm run dev

エラー3: 「NEXTAUTH_SECRET is not defined」

症状:

ページを開くとエラーが表示される。

原因:

.env.localNEXTAUTH_SECRETが設定されていない。

解決方法:

  1. ターミナルで以下を実行:
   openssl rand -base64 32
  1. 出力された文字列を.env.localNEXTAUTH_SECRETに貼り付ける
  2. 開発サーバーを再起動

エラー4: ポート3000が使用中

症状:

⚠ Port 3000 is in use by process 88973

原因:

すでに別のプロセスがポート3000を使っている。

解決方法:

Macの場合:

lsof -ti:3000 | xargs kill -9

Windowsの場合:

netstat -ano | findstr :3000
taskkill /PID プロセスID /F

または、別のポートで起動:

npm run dev -- -p 3001

この場合、.env.localNEXTAUTH_URLhttp://localhost:3001に変更してください。

エラー5: 「Module not found: Can't resolve '@/lib/auth'」

症状:

ファイルが見つからないというエラー。

原因:

ファイルパスが間違っている、またはファイルが存在しない。

解決方法:

  1. lib/auth.tsが存在するか確認
  2. ファイル名のスペルミスがないか確認
  3. 開発サーバーを再起動

まとめ

この記事でできたこと

✅ Twitter APIとは何かを理解した
✅ OAuth認証の仕組みを理解した
✅ Next.jsプロジェクトをセットアップした
✅ Twitter Developer Portalでアプリを登録した
✅ NextAuth.jsで認証システムを実装した
「Xでサインイン」ボタンが動くようになった

次の第2部では

第2部では、自分のツイートを表示する機能を実装します。

内容:

  • Twitter APIからデータを取得する方法
  • セッションとアクセストークンの使い方
  • ツイート一覧の表示
  • エラーハンドリングの基礎

第1部で認証ができているので、第2部はもっと簡単です!


参考資料

公式ドキュメント

OAuth 2.0について学ぶ

Twitter API関連

Next.js関連

お疲れさまでした!🎉

次の第2部もお楽しみに!