留意事項
メモ書き程度なので詳細な説明は割愛します。
使う技術やサービス
- Firebase Authentication
- Node.js(FWはExpress)
Firebase側の準備
プロジェクト作成し、Authentication -> Sign-in MethodでGoogleを有効にしておきます。
Firebase側の準備はこれで終わりです。
実装
firebase-toolsをグローバルではなくローカルにインストールして進めていきます(ちょっと訳あり)。
準備
適当なディレクトリを作って
$ npm init -y
$ npm i firebase-tools
$ npx firebase login
ログインが終わったら
$ npx firebase init
以下、ログ
? Are you ready to proceed? (y/N) y
? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices.
( ) Database: Deploy Firebase Realtime Database Rules
( ) Firestore: Deploy rules and create indexes for Firestore
(*) Functions: Configure and deploy Cloud Functions
(*) Hosting: Configure and deploy Firebase Hosting sites
( ) Storage: Deploy Cloud Storage security rules
( ) Emulators: Set up local emulators for Firebase features
? Please select an option:
> Use an existing project
Create a new project
Add Firebase to an existing Google Cloud Platform project
Don't set up a default project
? Select a default Firebase project for this directory: (Use arrow keys)
> hogehoge-xxxxx (hogehoge)
=== Functions Setup
A functions directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.
? What language would you like to use to write Cloud Functions? (Use arrow keys)
> JavaScript
TypeScript
? Do you want to use ESLint to catch probable bugs and enforce style? (y/N) n
+ Wrote functions/package.json
+ Wrote functions/index.js
+ Wrote functions/.gitignore
? Do you want to install dependencies with npm now? (Y/n) y
i Writing configuration info to firebase.json...
i Writing project information to .firebaserc...
i Writing gitignore file to .gitignore...
+ Firebase initialization complete!
バックエンド
functionsディレクトリ内で作業する。
$ npm i express cookie-parser
index.js
const admin = require("firebase-admin");
const cookieParser = require("cookie-parser")();
const express = require("express");
const functions = require("firebase-functions");
const api = express();
admin.initializeApp();
const validateFirebaseIdToken = async (req, res, next) => {
if (
(!req.headers.authorization ||
!req.headers.authorization.startsWith("Bearer ")) &&
!(req.cookies && req.cookies.__session)
) {
res.status(403).send("Unauthorized");
return;
}
let idToken;
if (
req.headers.authorization &&
req.headers.authorization.startsWith("Bearer ")
) {
console.log('Found "Authorization" header');
idToken = req.headers.authorization.split("Bearer ")[1];
} else if (req.cookies) {
console.log('Found "__session" cookie');
idToken = req.cookies.__session;
} else {
res.status(403).send("Unauthorized");
return;
}
try {
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
console.log("ID Token correctly decoded", decodedIdToken);
req.user = decodedIdToken;
next();
return;
} catch (err) {
console.error(err);
res.status(403).send("Unauthorized");
return;
}
};
api.use(cookieParser);
api.use(validateFirebaseIdToken);
api.get("/", (req, res) => {
res.status(200).send(JSON.stringify(req.user));
});
exports.api = functions.https.onRequest(api);
フロントエンド
publicディレクトリ内で作業する。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Sample</title>
</head>
<body>
<button id="demo-sign-in-button" style="display: none;">
Sign in with Google
</button>
<button id="demo-sign-out-button" style="display: none;">
Sign out
</button>
<script src="/__/firebase/7.14.2/firebase-app.js"></script>
<script src="/__/firebase/7.14.2/firebase-auth.js"></script>
<script src="/__/firebase/init.js"></script>
<script src="index.js"></script>
</body>
</html>
index.js
document.addEventListener("DOMContentLoaded", function () {
signInButton = document.getElementById("demo-sign-in-button");
signOutButton = document.getElementById("demo-sign-out-button");
signInButton.addEventListener("click", signIn);
signOutButton.addEventListener("click", signOut);
firebase.auth().onAuthStateChanged(onAuthStateChanged);
});
const onAuthStateChanged = (user) => {
if (user) {
console.info(user);
signInButton.style.display = "none";
signOutButton.style.display = "block";
startFunctionsCookieRequest();
} else {
console.info("!user");
signInButton.style.display = "block";
signOutButton.style.display = "none";
}
};
const signIn = () => {
firebase.auth().signInWithPopup(new firebase.auth.GoogleAuthProvider());
};
const signOut = () => {
firebase.auth().signOut();
document.cookie = "__session=";
};
const startFunctionsCookieRequest = () => {
firebase
.auth()
.currentUser.getIdToken(true)
.then(function (token) {
document.cookie = "__session=" + token + ";max-age=86400";
});
};
プロダクト開発ではAuthorization: Bearer <token>
を使うようにしよう(手抜き)。
動作確認
プロジェクトルートディレクトリで作業する。
$ npx firebase emulators:start
Hostingは5000番ポート、Functionsは5001番ポートで起動しますのでそれぞれ確認。