Firebase Authenticationとは
Firebase Authenticationは、Googleが提供するBaaS(Backend as a Service)の認証サービスです。メール/パスワード認証、OAuth認証、電話番号認証などを簡単に実装できます。
セットアップ
# npm
npm install firebase
# yarn
yarn add firebase
// lib/firebase.ts
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};
export const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
メール/パスワード認証
ユーザー登録
// hooks/useAuth.ts
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
sendPasswordResetEmail,
updateProfile,
} from 'firebase/auth';
import { auth } from '@/lib/firebase';
export function useAuth() {
const signUp = async (email: string, password: string, displayName: string) => {
try {
const { user } = await createUserWithEmailAndPassword(auth, email, password);
// プロフィール更新
await updateProfile(user, { displayName });
return { user, error: null };
} catch (error: any) {
return { user: null, error: getErrorMessage(error.code) };
}
};
const signIn = async (email: string, password: string) => {
try {
const { user } = await signInWithEmailAndPassword(auth, email, password);
return { user, error: null };
} catch (error: any) {
return { user: null, error: getErrorMessage(error.code) };
}
};
const logout = () => signOut(auth);
const resetPassword = async (email: string) => {
try {
await sendPasswordResetEmail(auth, email);
return { success: true, error: null };
} catch (error: any) {
return { success: false, error: getErrorMessage(error.code) };
}
};
return { signUp, signIn, logout, resetPassword };
}
// エラーメッセージの日本語化
function getErrorMessage(code: string): string {
const messages: Record<string, string> = {
'auth/email-already-in-use': 'このメールアドレスは既に使用されています',
'auth/invalid-email': '無効なメールアドレスです',
'auth/weak-password': 'パスワードは6文字以上で入力してください',
'auth/user-not-found': 'ユーザーが見つかりません',
'auth/wrong-password': 'パスワードが正しくありません',
'auth/too-many-requests': 'リクエストが多すぎます。しばらく待ってから再試行してください',
};
return messages[code] || '認証エラーが発生しました';
}
認証フォーム
// components/SignUpForm.tsx
'use client';
import { useState } from 'react';
import { useAuth } from '@/hooks/useAuth';
export function SignUpForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [name, setName] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const { signUp } = useAuth();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError('');
const { user, error } = await signUp(email, password, name);
if (error) {
setError(error);
} else {
// リダイレクトなど
}
setLoading(false);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="名前"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
<input
type="email"
placeholder="メールアドレス"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<input
type="password"
placeholder="パスワード(6文字以上)"
value={password}
onChange={(e) => setPassword(e.target.value)}
minLength={6}
required
/>
{error && <p className="error">{error}</p>}
<button type="submit" disabled={loading}>
{loading ? '登録中...' : 'アカウント作成'}
</button>
</form>
);
}
OAuth認証(Google/GitHub)
// hooks/useOAuth.ts
import {
signInWithPopup,
GoogleAuthProvider,
GithubAuthProvider,
} from 'firebase/auth';
import { auth } from '@/lib/firebase';
export function useOAuth() {
const signInWithGoogle = async () => {
const provider = new GoogleAuthProvider();
provider.addScope('profile');
provider.addScope('email');
try {
const { user } = await signInWithPopup(auth, provider);
return { user, error: null };
} catch (error: any) {
return { user: null, error: error.message };
}
};
const signInWithGitHub = async () => {
const provider = new GithubAuthProvider();
provider.addScope('read:user');
provider.addScope('user:email');
try {
const { user } = await signInWithPopup(auth, provider);
return { user, error: null };
} catch (error: any) {
return { user: null, error: error.message };
}
};
return { signInWithGoogle, signInWithGitHub };
}
// components/OAuthButtons.tsx
'use client';
import { useOAuth } from '@/hooks/useOAuth';
export function OAuthButtons() {
const { signInWithGoogle, signInWithGitHub } = useOAuth();
return (
<div className="oauth-buttons">
<button onClick={signInWithGoogle}>
Googleでログイン
</button>
<button onClick={signInWithGitHub}>
GitHubでログイン
</button>
</div>
);
}
認証状態の監視
// contexts/AuthContext.tsx
'use client';
import { createContext, useContext, useEffect, useState } from 'react';
import { onAuthStateChanged, User } from 'firebase/auth';
import { auth } from '@/lib/firebase';
type AuthContextType = {
user: User | null;
loading: boolean;
};
const AuthContext = createContext<AuthContextType>({
user: null,
loading: true,
});
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setUser(user);
setLoading(false);
});
return () => unsubscribe();
}, []);
return (
<AuthContext.Provider value={{ user, loading }}>
{children}
</AuthContext.Provider>
);
}
export const useUser = () => useContext(AuthContext);
// 使用例
function Dashboard() {
const { user, loading } = useUser();
if (loading) return <div>読み込み中...</div>;
if (!user) return <div>ログインしてください</div>;
return (
<div>
<p>ようこそ、{user.displayName}さん</p>
<img src={user.photoURL || ''} alt="プロフィール画像" />
</div>
);
}
保護されたルート
// components/ProtectedRoute.tsx
'use client';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { useUser } from '@/contexts/AuthContext';
export function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { user, loading } = useUser();
const router = useRouter();
useEffect(() => {
if (!loading && !user) {
router.push('/login');
}
}, [user, loading, router]);
if (loading) {
return <div>読み込み中...</div>;
}
if (!user) {
return null;
}
return <>{children}</>;
}
IDトークンの取得
// APIリクエスト時の認証
async function fetchWithAuth(url: string) {
const user = auth.currentUser;
if (!user) {
throw new Error('Not authenticated');
}
const token = await user.getIdToken();
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.json();
}
サーバーサイドでの検証
// api/protected/route.ts (Next.js)
import { getAuth } from 'firebase-admin/auth';
import { initializeApp, getApps, cert } from 'firebase-admin/app';
// Firebase Admin初期化
if (!getApps().length) {
initializeApp({
credential: cert({
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
}),
});
}
export async function GET(request: Request) {
const authorization = request.headers.get('Authorization');
if (!authorization?.startsWith('Bearer ')) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const token = authorization.split('Bearer ')[1];
try {
const decodedToken = await getAuth().verifyIdToken(token);
const userId = decodedToken.uid;
// ユーザー固有のデータを返す
return Response.json({ userId, data: '...' });
} catch (error) {
return Response.json({ error: 'Invalid token' }, { status: 401 });
}
}
メール確認
import { sendEmailVerification } from 'firebase/auth';
// メール確認送信
async function sendVerificationEmail() {
const user = auth.currentUser;
if (user && !user.emailVerified) {
await sendEmailVerification(user);
alert('確認メールを送信しました');
}
}
// 確認状態チェック
function isEmailVerified() {
return auth.currentUser?.emailVerified ?? false;
}
セキュリティルール(Firestore)
// firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// 認証済みユーザーのみ読み書き可能
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// 公開データ(誰でも読める)
match /public/{document=**} {
allow read: if true;
allow write: if request.auth != null;
}
}
}
関連記事
まとめ
Firebase Authenticationは、多様な認証方法を簡単に実装できるサービスです。クライアントサイドでの認証とサーバーサイドでの検証を組み合わせて、セキュアなアプリケーションを構築しましょう。
← 一覧に戻る