🌐
NextAuth.js + LINE Login による認証システムの実装
はじめに
この記事では、Next.jsアプリケーションにLINE Loginを実装する方法を、初学者でも理解できるように詳しく説明します。
認証とは?
認証(Authentication)とは、「この人は本当に本人なのか?」を確認する仕組みです。
- ログイン: 本人確認のための手続き
- セッション: ログイン状態を維持する仕組み
- ログアウト: セッションを終了する手続き
NextAuth.jsとは?
NextAuth.jsは、Next.jsアプリケーションで認証機能を簡単に実装できるライブラリです。
従来の方法(大変):
// 手動でOAuth認証を実装する場合
const authUrl = `https://access.line.me/oauth2/v2.1/authorize?` +
`response_type=code&` +
`client_id=${CLIENT_ID}&` +
`redirect_uri=${REDIRECT_URI}&` +
`state=${randomState}&` +
`scope=profile%20openid`;
// コールバック処理も手動で実装
app.get('/callback', async (req, res) => {
// 複雑な処理...
});
NextAuth.jsを使う場合(簡単):
// 設定だけで完結
LineProvider({
clientId: process.env.LINE_CLIENT_ID!,
clientSecret: process.env.LINE_CLIENT_SECRET!,
})
実装手順
- 必要なパッケージのインストール(パッケージマネージャはなんでもいいです)
補足:パッケージマネージャとは?pnpm install next-auth - NextAuth.jsの設定ファイル作成
app/api/auth/[...nextauth]/route.tsを作成:
import NextAuth from "next-auth";
import LineProvider from "next-auth/providers/line";
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
const handler = NextAuth({
// デバッグモードを有効化(開発時のみ)
debug: true,
// プロバイダー設定(LINE Login)
providers: [
LineProvider({
clientId: process.env.LINE_CLIENT_ID!,
clientSecret: process.env.LINE_CLIENT_SECRET!,
authorization: {
params: {
scope: "profile openid",
},
},
}),
],
// コールバック関数(認証の各段階で実行される)
callbacks: {
// ログイン時の処理
async signIn({ user, account, profile }) {
console.log('ログイン処理開始:', { user, account, profile });
if (account?.provider === 'line' && profile) {
try {
// データベースにユーザー情報を保存
await sql`
INSERT INTO users (id, name, created_at)
VALUES (${profile.sub}, ${profile.name}, NOW())
ON CONFLICT (id) DO NOTHING
RETURNING id
`;
console.log('ユーザー情報をデータベースに保存しました');
} catch (error) {
console.error('データベース保存エラー:', error);
}
}
return true; // ログインを許可
},
// JWTトークンに情報を追加
async jwt({ token, account, profile }) {
if (account && profile) {
token.lineUserId = profile.sub; // LINEのユーザーID
token.lineUserName = profile.name; // LINEのユーザー名
}
return token;
},
// セッションに情報を追加
async session({ session, token }) {
if (token.lineUserId) {
session.user.lineUserId = token.lineUserId as string;
session.user.lineUserName = token.lineUserName as string;
}
return session;
},
},
});
export { handler as GET, handler as POST };
- 型定義の拡張
types/next-auth.d.tsを作成:
import NextAuth from "next-auth"
declare module "next-auth" {
interface Session {
user: {
lineUserId: string
lineUserName: string
}
}
interface User {
lineUserId: string
lineUserName: string
}
}
declare module "next-auth/jwt" {
interface JWT {
lineUserId: string
lineUserName: string
}
}
- プロバイダーの設定
app/providers.tsxを作成:
'use client';
import { SessionProvider } from 'next-auth/react';
export default function Providers({ children }: { children: React.ReactNode }) {
return (
<SessionProvider>
{children}
</SessionProvider>
);
}
- レイアウトにプロバイダーを追加
app/layout.tsxを更新:
import Providers from './providers';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="ja">
<body>
<Providers>
{children}
</Providers>
</body>
</html>
);
}
- 認証フックの作成
hooks/useAuth.tsを作成:
'use client';
import { useEffect } from 'react';
import { useSession, signIn, signOut } from 'next-auth/react';
import { useAuthStore } from '@/stores/authStore';
export const useAuth = () => {
// NextAuth.jsのセッション情報を取得
const { data: session, status } = useSession();
const {
isLineBrowser,
user,
setLineBrowser,
setUser,
logout
} = useAuthStore();
// LINE内ブラウザかどうかを判定
useEffect(() => {
const userAgent = navigator.userAgent.toLowerCase();
const isLine = userAgent.includes('line');
setLineBrowser(isLine);
}, [setLineBrowser]);
// NextAuth.jsのセッション状態をZustandストアに同期
useEffect(() => {
if (status === 'loading') return;
if (session?.user?.lineUserId && session?.user?.lineUserName) {
setUser({
lineUserId: session.user.lineUserId,
lineUserName: session.user.lineUserName,
});
} else {
setUser(null);
}
}, [session, status, setUser]);
// LINE内ブラウザでの自動ログイン
useEffect(() => {
if (status === 'unauthenticated' && isLineBrowser) {
signIn('line');
}
}, [isLineBrowser, status]);
const handleSignIn = () => signIn('line');
const handleSignOut = () => {
signOut();
logout();
};
return {
isLineBrowser,
user,
isAuthenticated: !!user,
isLoading: status === 'loading',
signIn: handleSignIn,
signOut: handleSignOut,
};
};
- グローバル状態管理(Zustand)
stores/authStore.tsを作成:
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface User {
lineUserId: string;
lineUserName: string;
}
interface AuthState {
isLineBrowser: boolean;
user: User | null;
isAuthenticated: boolean;
setLineBrowser: (isLine: boolean) => void;
setUser: (user: User | null) => void;
logout: () => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
isLineBrowser: false,
user: null,
isAuthenticated: false,
setLineBrowser: (isLine) => set({ isLineBrowser: isLine }),
setUser: (user) => set({
user,
isAuthenticated: !!user
}),
logout: () => set({
user: null,
isAuthenticated: false
}),
}),
{
name: 'auth-storage', // ローカルストレージのキー名
}
)
);
- コンポーネントでの使用例
app/page.tsxでの使用:
'use client';
import { Button } from "@/components/ui/button";
import { useAuth } from "@/hooks/useAuth";
export default function Home() {
const { user, isLoading, signIn, signOut, isLineBrowser } = useAuth();
// ローディング中の表示
if (isLoading) {
return (
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto mb-4"></div>
<p className="text-gray-600">
{isLineBrowser ? 'LINEでログイン中...' : '認証情報を確認中...'}
</p>
</div>
);
}
// 未ログイン時の表示
if (!user) {
return (
<div className="text-center space-y-4">
<h2 className="text-xl font-bold">Planlyへようこそ</h2>
<p className="text-gray-600">
LINEアカウントでログインして、イベントの管理を始めましょう
</p>
<Button onClick={signIn} className="w-full">
LINEでログイン
</Button>
</div>
);
}
// ログイン済みの表示
return (
<div className="text-center space-y-4">
<h2 className="text-xl font-bold">ようこそ、{user.lineUserName}さん</h2>
<Button onClick={signOut}>
ログアウト
</Button>
</div>
);
}
- 最後に
環境変数の設定
.env.localファイルを作成:
# LINE Login設定
LINE_CLIENT_ID=your_line_client_id
LINE_CLIENT_SECRET=your_line_client_secret
# NextAuth.js設定
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your_secret_key
# データベース設定
DATABASE_URL=your_database_url
全体のデータの流れ
- ユーザーがログインボタンをクリック
- NextAuth.jsがLINEの認証画面にリダイレクト
- LINEで認証完了後、コールバックURLに戻る
- NextAuth.jsが認証情報を処理
- データベースにユーザー情報を保存
- セッションを作成してクッキーに保存
- アプリケーションでログイン状態を利用
Discussion