やること
Firebaseの3つの機能 Hosting(静的サイトのサーブ)、Auth(認証機能)、Functions(マイクロサービス)を連携させ、その可能性を探る。
- Firebase のHosting で静的サイトを公開するが、
- 特定のパス (
/hello
) は動的コンテンツであり、Firebase Functions の関数helloWorld
にディスパッチする -
helloWorld
は、 Firebase Auth と連携し認証ユーザに応じたレスポンスを返す -
index.html
では Firebase Auth を使ったサインイン/サインアウトを実装するとともに /hello にリクエストを投げるI/Fを付ける
前提
-
firebase-tools
をインストールしてfirebase deploy
で公開できる用意ができていること - 私の機能の記事 などを参考に、認証ユーザを事前に作成しておくこと。
スクリーンショット(index.html)
01. Functions の作成
helloWorld
を作ろう。リクエストのAuthorization
ヘッダからトークンを抜き出し、Firebase Auth の機能でトークンのベリフィケーションを実施。成功したら、ユーザーIDを含む文字列を返却する。
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
function tokenOf (req) {
if (!req.get('Authorization')) { return null; }
const x = req.get('Authorization').split('Bearer ');
return x.length === 2 ? x[1]: null;
}
exports.helloWorld = functions.https.onRequest((req, res) => {
const token = tokenOf(req);
if (!token) { res.status(401).send('not sign in'); }
return admin.auth().verifyIdToken(token)
.then(decoded => res.status(200).send(`hello ${decoded.uid}`))
.catch(err => res.status(401).send(err));
});
02. Functionsをルーティング
firebase.json にURLパスと、Functionsの対応関係をかく。rewrites フィールドに注目。これで、URL /hello
へのアクセスが、 helloWorld
にディスパッチされる。
{
"functions": {
"predeploy": [
"npm --prefix \"$RESOURCE_DIR\" run lint"
]
},
"hosting": {
"public": "public",
"rewrites": [ { "source": "/hello", "function": "helloWorld" }],
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
]
}
}
03. 認証機能付き静的ページの作成
defer を付けて非同期ロードでも順序は守って実行。自動生成してくれる init.js
の中でSDKの初期化がされる。
ロジックは全部 main.js
に書こう。
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script defer src="/__/firebase/5.0.2/firebase-app.js"></script>
<script defer src="/__/firebase/5.0.2/firebase-auth.js"></script>
<!--
<script defer src="/__/firebase/5.0.2/firebase-database.js"></script>
<script defer src="/__/firebase/5.0.2/firebase-messaging.js"></script>
<script defer src="/__/firebase/5.0.2/firebase-storage.js"></script>
-->
<script defer src="/__/firebase/init.js"></script>
<script defer src="main.js"> </script>
</head>
<body>
<input id="userID" type="text" value=""/>
<input id="userPass" type="password" value=""/>
<button id="signInBtn">SignIn</button>
<button id="signOutBtn">SignOut</button>
<button id="hello">InvokeHello</button>
</body>
</html>
以前にサインインしていれば、明示的なサインインsignInWithEmailAndPassword
をしなくてもよい。observeAuthState()
のような実装で、DOMを書き換えてサインインをしなくてもよいことを知らせるなどの工夫をするのがよさそう。hello()
の実装のように、currentUserが非null ならトークンがゲットできるはずなので、それをヘッダにセットして、Functions にディスパッチされるURL /hello にリクエストを投げる。
function log (msg) {
console.log(msg);
}
function observeAuthState () {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
log(`sign in as ${user.uid}.`);
} else {
log(`sign out.`);
}
});
}
function signIn () {
const email = document.getElementById('userID').value;
const password = document.getElementById('userPass').value;
firebase.auth().signInWithEmailAndPassword(email, password)
.then(() => log('login sucess'))
.catch(log);
}
function signOut () {
firebase.auth().signOut();
}
async function hello () {
const me = firebase.auth().currentUser;
if (!me) { log('not signed in.'); return; }
const token = await me.getIdToken();
const resp = await fetch('/hello', { headers: { 'Authorization': `Bearer ${token}` } });
resp.text().then(log);
}
// Listener
document.addEventListener('DOMContentLoaded', observeAuthState);
document.getElementById('signInBtn').addEventListener('click', signIn);
document.getElementById('signOutBtn').addEventListener('click', signOut);
document.getElementById('hello').addEventListener('click', hello);
まとめ
Firebase まじですごい!