Firebase JWT
ライブラリ
Firebase JWT
概要
Firebase JWTは、Firebase Authenticationサービスが提供するJSON Web Token(JWT)ベースの認証システムです。
詳細
Firebase JWTは、Firebase Authenticationサービスが生成・管理するJWT(JSON Web Token)形式の認証トークンを使用して、ユーザー認証を行うシステムです。Firebase JavaScriptクライアントSDKとFirebase Admin SDKを通じて、クライアントサイドでのトークン取得からサーバーサイドでのトークン検証まで、包括的な認証フローを提供します。
Firebase IDトークンは、ユーザーがFirebaseにサインインした際に生成される暗号化されたJWTです。これらのトークンには、ユーザーのUID、メールアドレス、サインインプロバイダーなどの基本的なプロフィール情報が含まれています。トークンはFirebaseの秘密鍵で署名されており、Firebase Admin SDKやサードパーティのJWTライブラリを使用して検証できます。
Virtual DOMという仮想的なDOM表現の代わりに、Firebase JWTはVirtual DOMという仮想的なトークン表現を使用することで、実際のデータベースアクセスを最小限に抑え、高いセキュリティを実現しています。トークンベースの認証により、セッション管理が不要になり、ステートレスで拡張可能なアプリケーション開発が可能です。
現在では世界最大のクラウドプラットフォームGoogle Cloudのエコシステムを持ち、Firebase Authentication、Firebase Firestore、Firebase Functions、Firebase Hostingなど多くの関連サービスと組み合わせて使用されています。
メリット・デメリット
メリット
- ステートレス認証: セッション状態の管理が不要で、スケーラブルなアーキテクチャ
- 自動トークン更新: SDKが自動的にトークンの有効期限を管理・更新
- マルチプラットフォーム対応: Web、iOS、Android、Node.jsで統一的な認証体験
- 豊富な認証プロバイダー: Google、Facebook、Twitter、GitHub等の外部認証サービス連携
- カスタムクレーム: アプリケーション固有の権限情報をトークンに追加可能
- Firebase エコシステム: Firestore、Functions、Hosting等との完全統合
- 開発効率: 認証基盤の構築・運用コストを大幅削減
デメリット
- Firebase依存: Firebase以外の認証システムとの統合が困難
- カスタマイズ制限: 認証フローのカスタマイズに制限がある
- コスト: 大規模ユーザー数での従量課金
- ベンダーロックイン: 他のクラウドプロバイダーへの移行が困難
- デバッグの複雑さ: トークン関連のエラーの原因特定が困難な場合がある
主要リンク
- Firebase Authentication公式サイト
- Firebase JavaScript SDK
- Firebase Admin SDK
- Firebase IDトークンの検証
- Firebase GitHub リポジトリ
- Firebase コミュニティ
書き方の例
基本的なサインイン・トークン取得
import { initializeApp } from 'firebase/app';
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';
// Firebaseの初期化
const firebaseConfig = {
// 設定情報
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
// ユーザーサインイン
const signInUser = async (email, password) => {
try {
const userCredential = await signInWithEmailAndPassword(auth, email, password);
const user = userCredential.user;
// IDトークンの取得
const idToken = await user.getIdToken();
console.log('IDトークン:', idToken);
return user;
} catch (error) {
console.error('サインインエラー:', error);
throw error;
}
};
トークンの強制更新と詳細情報取得
import { getAuth } from 'firebase/auth';
const auth = getAuth();
// 現在のユーザーのトークン情報を取得
const getUserTokenInfo = async () => {
const user = auth.currentUser;
if (!user) {
throw new Error('ユーザーがサインインしていません');
}
try {
// トークンの強制更新
const idToken = await user.getIdToken(true);
// トークンの詳細情報を取得
const idTokenResult = await user.getIdTokenResult();
console.log('IDトークン:', idToken);
console.log('発行者:', idTokenResult.claims.iss);
console.log('ユーザーID:', idTokenResult.claims.sub);
console.log('有効期限:', new Date(idTokenResult.claims.exp * 1000));
console.log('カスタムクレーム:', idTokenResult.claims);
return {
token: idToken,
claims: idTokenResult.claims,
expirationTime: idTokenResult.expirationTime
};
} catch (error) {
console.error('トークン取得エラー:', error);
throw error;
}
};
サーバーサイドでのトークン検証(Node.js Admin SDK)
const admin = require('firebase-admin');
// Admin SDKの初期化
const serviceAccount = require('./path/to/serviceAccountKey.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
// IDトークンの検証
const verifyIdToken = async (idToken) => {
try {
// トークンの検証とデコード
const decodedToken = await admin.auth().verifyIdToken(idToken);
console.log('ユーザーID:', decodedToken.uid);
console.log('メールアドレス:', decodedToken.email);
console.log('認証プロバイダー:', decodedToken.firebase.sign_in_provider);
console.log('認証時刻:', new Date(decodedToken.auth_time * 1000));
return decodedToken;
} catch (error) {
console.error('トークン検証エラー:', error);
throw new Error('無効なトークンです');
}
};
// Express.jsミドルウェアでの使用例
const authenticateToken = async (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // "Bearer TOKEN"
if (!token) {
return res.status(401).json({ error: 'アクセストークンが必要です' });
}
try {
const decodedToken = await verifyIdToken(token);
req.user = decodedToken;
next();
} catch (error) {
return res.status(403).json({ error: 'トークンが無効です' });
}
};
カスタムクレームの設定と活用
const admin = require('firebase-admin');
// カスタムクレームの設定(管理者権限)
const setAdminClaim = async (uid) => {
try {
await admin.auth().setCustomUserClaims(uid, {
admin: true,
role: 'administrator',
permissions: ['read', 'write', 'delete']
});
console.log(`ユーザー ${uid} に管理者権限を付与しました`);
} catch (error) {
console.error('カスタムクレーム設定エラー:', error);
throw error;
}
};
// クライアントサイドでのカスタムクレーム確認
const checkUserRole = async () => {
const user = auth.currentUser;
if (!user) return null;
try {
// トークンを強制更新してカスタムクレームを取得
const idTokenResult = await user.getIdTokenResult(true);
const isAdmin = idTokenResult.claims.admin || false;
const userRole = idTokenResult.claims.role || 'user';
const permissions = idTokenResult.claims.permissions || [];
return {
isAdmin,
role: userRole,
permissions
};
} catch (error) {
console.error('ロール確認エラー:', error);
return null;
}
};
リアルタイム認証状態監視
import { getAuth, onAuthStateChanged } from 'firebase/auth';
const auth = getAuth();
// 認証状態の監視
const setupAuthListener = () => {
return onAuthStateChanged(auth, async (user) => {
if (user) {
// ユーザーがサインイン中
console.log('ユーザーサインイン:', user.uid);
try {
// 最新のトークンを取得
const idToken = await user.getIdToken();
// APIリクエストのヘッダーに設定
setAuthHeader(idToken);
// ユーザー情報の更新
updateUserUI(user);
} catch (error) {
console.error('トークン取得エラー:', error);
}
} else {
// ユーザーがサインアウト
console.log('ユーザーサインアウト');
clearAuthHeader();
redirectToLogin();
}
});
};
// APIリクエスト用のヘッダー設定
const setAuthHeader = (token) => {
// Axiosの場合
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
// Fetchの場合は、リクエスト毎に設定
window.firebaseToken = token;
};
// サインアウト処理
const signOutUser = async () => {
try {
await auth.signOut();
console.log('サインアウト完了');
} catch (error) {
console.error('サインアウトエラー:', error);
}
};
Firebase Functions での認証確認
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
// HTTP関数での認証確認
exports.secureFunction = functions.https.onCall(async (data, context) => {
// 認証状態の確認
if (!context.auth) {
throw new functions.https.HttpsError(
'unauthenticated',
'この関数には認証が必要です'
);
}
const uid = context.auth.uid;
const email = context.auth.token.email;
// カスタムクレームの確認
const isAdmin = context.auth.token.admin === true;
if (!isAdmin) {
throw new functions.https.HttpsError(
'permission-denied',
'管理者権限が必要です'
);
}
// 認証済みユーザーのみの処理
try {
const result = await performAdminOperation(data, uid);
return { success: true, data: result };
} catch (error) {
throw new functions.https.HttpsError(
'internal',
'処理中にエラーが発生しました'
);
}
});