Pinar Ozlen
Software Engineer
本日(元記事執筆時点)は、Firebase Cloud Messaging(FCM)で JavaScript ライブラリがリリースされ、ウェブがサポートされたことをお知らせいたします。これによって、現在のブラウザのサポートが拡大され、実装プロセスも劇的にシンプルになります。また、
トピック や
端末グループへのメッセージング などの強力な機能をウェブでも使えるようになります。
通知は、デベロッパーが魅力的な仕組みを作る上で欠かせないツールの 1 つです。Chrome にこの仕組みが導入されてから、とても広い範囲で採用されるようになっており、日々 100 億件を超える通知がウェブサイトに送信されています。ただ、デベロッパーからは、この機能をウェブで実装するのは難易度が高い場合があり、ネイティブ通知で利用できるのと同じような FCM の高度な機能にアクセスしたいという声もよく聞かれました。
Firebase Cloud Messaging は、既に iOS アプリ、Android アプリ、Chrome へのメッセージ送信サポートしている強力なシステムです。本日(元記事執筆時点)より、デベロッパーの皆様は、FCM を使って
Push API をサポートしているブラウザにメッセージを送信できるようになります。これによって、Chrome だけでなく、Firefox、Opera などにも通知を送れるようになります。
FCM JavaScript ライブラリによってウェブユーザーへの通知の送信も今まで以上に簡単になっています。FCM は、
ペイロードの暗号化 など複雑なサーバーサイドの機能や、
Service Worker などのクライアントサイドの機能にも対応しています。
すぐに使い始めることができるように、デフォルトの Service Worker 実装が準備されています。これは、拡張したりオーバーライドしたりすることによって、簡単に使うことができます。さらに、FCM API を使用すると、サーバーでペイロードの暗号化を行うことができます。FCM ユーザーは、サーバー実装をまったく変更せずにこれを実現できます。
ただし、ウェブ通知の技術的な側面はまだ始まったばかりです。ウェブ通知を最大限に活用するためには、ユーザーに正しい方法で正しいコンテンツを使ってもらう必要があります。通知内容に関するベスト プラクティスについては、「
よい通知とは 」や「
プッシュ通知許可 UX のベスト プラクティス 」をご覧ください。ウェブユーザーとやり取りして通知を送信する許可を得るためのおすすめの方法が記載されています。
サポートされている FCM 機能
FCM JavaScript ライブラリは、簡単なクライアント実装の提供に留まらず、ウェブ向けの重要な FCM 機能も実現しています。
FCM JavaScript ライブラリを使うと、単一の端末、トピック、端末のグループに対してウェブでプッシュ通知を送信できます。ウェブでトピックがサポートされたことに加え、特定のトピックをオプトインしている Android、iOS、ウェブの各ユーザーにメッセージを送信できます。トピックや端末グループのメリットを活用するには、サーバーサイド API を使って
トピック と
グループのサブスクリプション の管理を行う必要があります。
対象となるブラウザ
現在、FCM JavaScript ライブラリは、Push API をサポートしているブラウザで使用できます。具体的には、以下のブラウザになります。
PC およびモバイル向け Chrome(バージョン 50 以上)
PC およびモバイル向け Firefox Desktop(バージョン 44 以上)
モバイル版 Opera(バージョン 37 以上)
Microsoft Edge は Push API をサポートする予定であることを発表しています。Samsung Browser も、メッセージ ペイロードをサポートすれば対象になります。今後、Service Worker をサポートするブラウザが増えれば、対象となるブラウザは増えてゆくでしょう。アップデートについては、
リリースノート をご覧ください。
使ってみるには
Firecast の動画を確認し、
スタートガイド に従ってください。
VIDEO
パートナーの実績
私たちは、最高のユーザーのウェブ通知エクスペリエンスを実現すべく、アーリー アダプターとともに、テストや微修正を行い、新しい FCM JavaScript ライブラリの力を解放してきました。以下で、いくつかの成功事例を紹介します。
「FCM を見つけるまで、効果的な通知ソリューションを探すことはできませんでした。……機能が豊富でパフォーマンスも安定しており、導入も簡単な FCM は最高のソリューションです」
Alibaba.com モバイル、ディレクター、Zou Yu 氏
外国の購入者と中国のサプライヤーをつなぐ主要小売りマーケットプレイスである Alibaba.com は、2 日でこのソリューションを実装し、
ウェブ通知を受け取っているユーザーのエンゲージメントを直接ウェブサイトを訪れたユーザーの 4 倍に向上させました 。
Alibaba.com の事例紹介はこちらからご覧ください 。
「Firebase Cloud Messaging は私たちの要件に完璧に一致しました」
AliExpress、ディレクター、Lijun Chen 氏
グローバル小売りマーケットプレイスの AliExpress では、
ウェブでの通知はアプリの通知に比べて開封率が 93.4% 高く、通知を受信していないモバイルサイトのユーザーに比べてコンバージョン率が 178% 高くなっています 。AliExpress のチームは、現在もユースケースを展開中です。たとえば、11 月 11 日(「ダブル 11」とも言われます)は、主に若い独身の中国人が祝う世界最大級のオンライン ショッピング イベントです。この日の販売を促進するため、AliExpress はウェブサイト上で FCM を使って通知を送り、ユーザーが興味のある品を割引価格で購入できることをお知らせする予定です。
AliExpress の事例紹介はこちらからご覧ください 。
「初期実装はとても簡単でした。実際に 1 日で終わってしまったほどです」
デベロッパー、Filip Procházka 氏
ユーザーの共同購入をトラッキングする成長著しい新興企業 Settle Up は、請求書に変更があった場合にユーザーに通知を送信したいと考えていました。実装は 1 日で終わりましたが、
ウェブ通知を受信したユーザーのエンゲージメントが 37% 上昇 するという成果が現れ始めています。さらに、Firebase Analytics、Crash Reporting、Hosting、Test Lab を利用している Settle Up は、統合ソリューションとしての Firebase を通してすべてのツールに簡単にアクセスすることができます。
Settle Up の事例紹介はこちらからご覧ください 。
使ってみる
Firebase Cloud Messaging は、Firebase プラットフォームの一部であり、無償で使用できます。今回のリリースについてお知らせできることをうれしく思います。ぜひ感想をお寄せください。お待ちしています。
Posted by
Eiji Kitamura - Developer Relations Team
[この記事はソフトウェアエンジニア Pinar Ozlen による The Firebase Blog の記事 "Announcing Firebase Cloud Messaging for Web " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。]
Pinar Ozlen
Software Engineer
本日(元記事執筆時点)は、Firebase Cloud Messaging(FCM)で JavaScript ライブラリがリリースされ、ウェブがサポートされたことをお知らせいたします。これによって、現在のブラウザのサポートが拡大され、実装プロセスも劇的にシンプルになります。また、トピック や端末グループへのメッセージング などの強力な機能をウェブでも使えるようになります。
通知は、デベロッパーが魅力的な仕組みを作る上で欠かせないツールの 1 つです。Chrome にこの仕組みが導入されてから、とても広い範囲で採用されるようになっており、日々 100 億件を超える通知がウェブサイトに送信されています。ただ、デベロッパーからは、この機能をウェブで実装するのは難易度が高い場合があり、ネイティブ通知で利用できるのと同じような FCM の高度な機能にアクセスしたいという声もよく聞かれました。
Firebase Cloud Messaging は、既に iOS アプリ、Android アプリ、Chrome へのメッセージ送信サポートしている強力なシステムです。本日(元記事執筆時点)より、デベロッパーの皆様は、FCM を使って Push API をサポートしているブラウザにメッセージを送信できるようになります。これによって、Chrome だけでなく、Firefox、Opera などにも通知を送れるようになります。
FCM JavaScript ライブラリによってウェブユーザーへの通知の送信も今まで以上に簡単になっています。FCM は、ペイロードの暗号化 など複雑なサーバーサイドの機能や、Service Worker などのクライアントサイドの機能にも対応しています。
すぐに使い始めることができるように、デフォルトの Service Worker 実装が準備されています。これは、拡張したりオーバーライドしたりすることによって、簡単に使うことができます。さらに、FCM API を使用すると、サーバーでペイロードの暗号化を行うことができます。FCM ユーザーは、サーバー実装をまったく変更せずにこれを実現できます。
ただし、ウェブ通知の技術的な側面はまだ始まったばかりです。ウェブ通知を最大限に活用するためには、ユーザーに正しい方法で正しいコンテンツを使ってもらう必要があります。通知内容に関するベスト プラクティスについては、「よい通知とは 」や「プッシュ通知許可 UX のベスト プラクティス 」をご覧ください。ウェブユーザーとやり取りして通知を送信する許可を得るためのおすすめの方法が記載されています。
サポートされている FCM 機能
FCM JavaScript ライブラリは、簡単なクライアント実装の提供に留まらず、ウェブ向けの重要な FCM 機能も実現しています。
FCM JavaScript ライブラリを使うと、単一の端末、トピック、端末のグループに対してウェブでプッシュ通知を送信できます。ウェブでトピックがサポートされたことに加え、特定のトピックをオプトインしている Android、iOS、ウェブの各ユーザーにメッセージを送信できます。トピックや端末グループのメリットを活用するには、サーバーサイド API を使って トピック とグループのサブスクリプション の管理を行う必要があります。
対象となるブラウザ
現在、FCM JavaScript ライブラリは、Push API をサポートしているブラウザで使用できます。具体的には、以下のブラウザになります。
PC およびモバイル向け Chrome(バージョン 50 以上)
PC およびモバイル向け Firefox Desktop(バージョン 44 以上)
モバイル版 Opera(バージョン 37 以上)
Microsoft Edge は Push API をサポートする予定であることを発表しています。Samsung Browser も、メッセージ ペイロードをサポートすれば対象になります。今後、Service Worker をサポートするブラウザが増えれば、対象となるブラウザは増えてゆくでしょう。アップデートについては、リリースノート をご覧ください。
使ってみるには
Firecast の動画を確認し、スタートガイド に従ってください。
VIDEO
パートナーの実績
私たちは、最高のユーザーのウェブ通知エクスペリエンスを実現すべく、アーリー アダプターとともに、テストや微修正を行い、新しい FCM JavaScript ライブラリの力を解放してきました。以下で、いくつかの成功事例を紹介します。
「FCM を見つけるまで、効果的な通知ソリューションを探すことはできませんでした。……機能が豊富でパフォーマンスも安定しており、導入も簡単な FCM は最高のソリューションです」
Alibaba.com モバイル、ディレクター、Zou Yu 氏
外国の購入者と中国のサプライヤーをつなぐ主要小売りマーケットプレイスである Alibaba.com は、2 日でこのソリューションを実装し、ウェブ通知を受け取っているユーザーのエンゲージメントを直接ウェブサイトを訪れたユーザーの 4 倍に向上させました 。Alibaba.com の事例紹介はこちらからご覧ください 。
「Firebase Cloud Messaging は私たちの要件に完璧に一致しました」
AliExpress、ディレクター、Lijun Chen 氏
グローバル小売りマーケットプレイスの AliExpress では、ウェブでの通知はアプリの通知に比べて開封率が 93.4% 高く、通知を受信していないモバイルサイトのユーザーに比べてコンバージョン率が 178% 高くなっています 。AliExpress のチームは、現在もユースケースを展開中です。たとえば、11 月 11 日(「ダブル 11」とも言われます)は、主に若い独身の中国人が祝う世界最大級のオンライン ショッピング イベントです。この日の販売を促進するため、AliExpress はウェブサイト上で FCM を使って通知を送り、ユーザーが興味のある品を割引価格で購入できることをお知らせする予定です。AliExpress の事例紹介はこちらからご覧ください 。
「初期実装はとても簡単でした。実際に 1 日で終わってしまったほどです」
デベロッパー、Filip Procházka 氏
ユーザーの共同購入をトラッキングする成長著しい新興企業 Settle Up は、請求書に変更があった場合にユーザーに通知を送信したいと考えていました。実装は 1 日で終わりましたが、ウェブ通知を受信したユーザーのエンゲージメントが 37% 上昇 するという成果が現れ始めています。さらに、Firebase Analytics、Crash Reporting、Hosting、Test Lab を利用している Settle Up は、統合ソリューションとしての Firebase を通してすべてのツールに簡単にアクセスすることができます。Settle Up の事例紹介はこちらからご覧ください 。
使ってみる
Firebase Cloud Messaging は、Firebase プラットフォームの一部であり、無償で使用できます。今回のリリースについてお知らせできることをうれしく思います。ぜひ感想をお寄せください。お待ちしています。
Posted by Eiji Kitamura - Developer Relations Team
Nicolas Garnier
Developer Programs Engineer
Firebase Authentication は、4 つのすぐに使える
ID プロバイダ をサポートしているため、Google、Facebook、Twitter、GitHub で認証を行うのはとても簡単です。たとえば、ウェブアプリで Firebase ユーザーに Google でログインしてもらうために必要なのは次のコードだけです。
var google = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(google);
ただ、その他の ID プロバイダを使って独自の Firebase アプリにログインしてもらいたい場合もあるかもしれません。たとえば、Instagram API を使ってユーザーが Instagram の写真を共有できるようにするつもりなら、
Instagram はすばらしい選択肢になるでしょう。
Firebase にビルトインでサポートされていない ID プロバイダを使うことも可能ですが、それには少しばかりのコードとサーバーが必要になります。ここでは、Instagram によるログインを Firebase ウェブアプリに組み込むために必要な手順について、順に説明します。Instagram はログインに OAuth 2.0 を使っています。そのため、本投稿は LinkedIn のようなその他の OAuth 2.0 ID プロバイダによるログイン機能を組み込む際にも役立つはずです。
設計の概要
Instagram は OAuth 2.0 をサポートしています。これは、アプリの認証や Instagram のユーザー ID を含むユーザーデータにアクセスするための主な方法となります。必要になるのは、ユーザーに OAuth 2.0 認証コードのフローを実行してもらい、アプリへのアクセス権を付与することです。OAuth 2.0 のフローは、次のような流れで実行されます。
まず、ユーザーを Instagram の
認証 エンドポイントにリダイレクトする必要があります。エンドポイントでは、初めてアプリにアクセス権を付与する際に、ユーザーに同意を求める画面が表示されます。これは、ポップアップ ウィンドウで実現されています。
アプリの認証が完了すると、ユーザーは認証コードとともに元のドメインにリダイレクトされます。Instagram アプリの認証情報を使うと、サーバー側で
認証コード を
アクセス トークン と交換できます。Instagram では、認証コードを交換する過程でユーザー ID も返されます(LinkedIn などの他の OAuth 2.0 プロバイダでは、追加のリクエストが必要になる場合もあります)。
Instagram のユーザー情報を取得すると、サーバーで Firebase の
カスタム認証トークン を作成できるようになります。ユーザーは、このトークンと
signInWithCustomToken メソッドを使ってウェブアプリで Firebase にログインできます。
また、クライアント上で Firebase のプロフィールをアップデートできるように、表示名や写真の URL などの Instagram から取得したユーザー プロフィール情報も渡しています。 -
注: Instagram は、ユーザーのメールアドレスは提供していません。そのため、メールアドレスなしの Firebase アカウントができることになりますが、このこと自体には特に問題はありません。 以上が完了すると、ポップアップが閉じます。これで、ユーザーは Instagram アカウントのプロフィール データつきで Firebase にログインできました。
構築に着手する
では、もう少し詳細に踏み込み、重要な部分の実装方法を見てみましょう。今回は、バックエンドを Node.js で記述します。
Instagram にアプリを登録する
ウェブアプリには、Instagram 認証フローを開始するボタンが必要になります。その前に、OAuth 2.0 に必要なアプリの認証情報を取得できるよう、まず
Instagram デベロッパー コンソールにアプリケーションを登録する必要があります。
Instagram アプリの設定で、
http://localhost:8080/instagram-callback(テスト用)と
https:///instagram-callback (本番用ドメイン)を有効なリダイレクト URI としてホワイトリスト登録しておきます。次に、Instagram
クライアント ID と
クライアント シークレット をメモします。後でこれが必要になります。
アプリを登録し、コールバック URL をホワイトリストに登録すると、クライアント ID とクライアント シークレットが返されます。この流れは、どの OAuth 2.0 プロバイダでも必要となる典型的な手順です。
Instagram の OAuth 2.0 を設定する
サーバーでは、OAuth 2.0 プロトコルの詳細を隠蔽してくれる
simple-oauth2 パッケージを使います。これを設定するためには、Instagram クライアント ID とクライアント シークレット、Instagram の OAuth 2.0 トークンと認証エンドポイントなど、いくつかの値を設定する必要があります。Instagram に対して使う必要があるのは、次の値です。
// Instagram OAuth 2 の設定
const credentials = {
client: {
id:YOUR_INSTAGRAM_CLIENT_ID, // 要変更
secret:YOUR_INSTAGRAM_CLIENT_SECRET, // 要変更
},
auth: {
tokenHost: 'https://api.instagram.com',
tokenPath: '/oauth/access_token'
}
};
const oauth2 = require('simple-oauth2').create(credentials);
Instagram 認証フローを開始する
ユーザーを Instagram の同意画面にリダイレクトする URL ハンドラをサーバーに追加します。その際に、Instagram 認証フローを完了したときにユーザーがリダイレクトされて戻ってくる場所となる
リダイレクト URI を提供する必要があります。今回は、
/instagram-callback をコールバック ハンドラのパスに使用します。
app.get('/redirect', (req, res) => {
// ランダムな状態検証 Cookie を生成
const state = req.cookies.state || crypto.randomBytes(20).toString('hex');
// localhost で安全でない Cookie を許可
const secureCookie = req.get('host').indexOf('localhost:') !== 0;
res.cookie('state', state.toString(), {maxAge: 3600000, secure: secureCookie, httpOnly: true});
const redirectUri = oauth2.authorizationCode.authorizeURL({
redirect_uri: `${req.protocol}://${req.get('host')}/instagram-callback`,
scope: 'basic',
state: state
});
res.redirect(redirectUri);
});
また、
セッション固定攻撃 を避けるため、OAuth リクエストの state パラメータでランダムな文字列を渡し、それを HTTP Cookie として保存しています。これによって、戻された state パラメータと Cookie に保存されている値を比較することができ、アプリがフローを開始したことを確認できます。
クライアントでは、ポップアップを表示するボタンとして、次のようなコードを書きます。
function onSignInButtonClick() {
// ポップアップで Auth フローをオープン
window.open('/redirect', 'firebaseAuth', 'height=315,width=400');
};
ユーザーがログインボタンをクリックすると、ポップアップがオープンし、ユーザーが Instagram の同意画面にリダイレクトされます。
同意したユーザーには URL の
code クエリ パラメータに認証コードが渡され、先ほど渡した
state の値と合わせて、
/instagram-callback URL ハンドラにリダイレクトされます。
アクセス トークン用の認証コードの交換
ユーザーがコールバック URL にリダイレクトされた際には、以下の処理を行います。
state Cookie が URL の state クエリ パラメータと同じであることを確認
アクセス トークン用の認証コードを交換し、Instagram からユーザー ID を取得
app.get('/instagram-callback',(req, res) => {
// state Cookie を受け取ったことを確認
if (!req.cookies.state) {
res.status(400).send('State cookie not set or expired.Maybe you took too long to authorize.Please try again.');
// state Cookie が state パラメータと一致することを確認
} else if (req.cookies.state !== req.query.state) {
res.status(400).send('State validation failed');
}
// アクセス トークン用に認証コードを交換
oauth2.authorizationCode.getToken({
code: req.query.code,
redirect_uri: `${req.protocol}://${req.get('host')}/instagram-callback`
}).then(results => {
// Instagram アクセス トークンとユーザー ID の取得が完了
const accessToken = results.access_token;
const instagramUserID = results.user.id;
const profilePic = results.user.profile_picture;
const userName = results.user.full_name;
// ...
});
});
これでこの実装での OAuth 2.0 固有部分は完成です。以降は、ほぼ Firebase に固有の部分です。
次は、Firebase カスタム認証トークンを作成します。そのカスタム認証トークンを使ってログインを実行し、Firebase のユーザー プロフィールをアップデート(詳細は後述)する HTML ページを提供します。
app.get('/instagram-callback', (req, res) => {
// ...
}).then(results => {
// Instagram アクセス トークンとユーザー ID の取得が完了
const accessToken = results.access_token;
const instagramUserID = results.user.id;
const profilePic = results.user.profile_picture;
const userName = results.user.full_name;
// Firebase カスタム認証トークンの作成
const firebaseToken = createFirebaseToken(instagramUserID);
// ログインを実行し、ユーザー プロフィールをアップデートする HTML ページの提供
res.send(
signInFirebaseTemplate(firebaseToken, userName, profilePic, accessToken)));
});
});
カスタム認証トークンの作成
Firebase カスタム認証トークンを作成するには、Firebase に
サービス アカウント認証情報 を設定する必要があります。これは、このようなトークンを生成する際に必要となる管理権限を付与するために必要になります。サービス アカウント認証情報ファイルは、
service-account.json として保存してください。
const firebase = require('firebase');
const serviceAccount = require('./service-account.json');
firebase.initializeApp({
serviceAccount: serviceAccount
});
カスタム認証トークンの作成はシンプルです。Instagram のユーザー ID に基づいて uid を選択するだけで構いません。
function createFirebaseToken(instagramID) {
// ユーザーに割り当てる uid
const uid = `instagram:${instagramID}`;
// カスタム トークンの作成
return firebase.auth().createCustomToken(uid);
}
注: サービス アカウント認証情報は安全に保管する必要があるため、カスタム トークンの作成は必ずサーバー側で行います。
カスタム トークンを作成した後は、それをクライアントに渡して Firebase にログインします。
カスタム トークンによるログイン
この時点で、サーバーはポップアップ ウィンドウ内で実行される HTML ページを提供し、以下の処理を行います。
後ほど Instagram API にアクセスする場合に備え、Instagram アクセス トークンを Realtime Database に保存(注: 対象のユーザーのみが読み取れるようにセキュリティ ルールを使用します)
Firebase ユーザー名とプロフィール写真のアップデート
ログインの実行とポップアップのクローズ
ここでポイントとなるのは、プロフィールをアップデートする際にデフォルトの Firebase アプリを使うのではなく、一時的な
Firebase App インスタンスを使っている点です。これによって、ユーザー プロフィールがアップデートされる前にメインページの Auth リスナーが呼ばれることを防いでいます。
app.get('/instagram-callback', (req, res) => {
// ...
// ログインを実行し、ユーザー プロフィールをアップデートする HTML ページの提供
res.send(
signInFirebaseTemplate(firebaseToken, userName, profilePic, accessToken)));
});
});
function signInFirebaseTemplate(token, displayName, photoURL, instagramAccessToken) {
return `
<script src="https://www.gstatic.com/firebasejs/3.4.0/firebase.js"></script>
<script src="promise.min.js"></script><!-- Promise Polyfill for older browsers -->
<script>
var token = '${token}';
var config = {
apiKey:MY_FIREBASE_API_KEY, // 要変更
databaseURL:MY_DATABASE_URL // 要変更
};
// 一時 Firebase アプリにログインしてプロフィールをアップデート
var tempApp = firebase.initializeApp(config, '_temp_');
tempApp.auth().signInWithCustomToken(token).then(function(user) {
// Realtime Database に Instagram API アクセス トークンを保存
const tasks = [tempApp.database().ref('/instagramAccessToken/' + user.uid)
.set('${instagramAccessToken}')];
// 必要に応じて displayname と photoURL をアップデート
if ('${displayName}' !== user.displayName || '${photoURL}' !== user.photoURL) {
tasks.push(user.updateProfile({displayName: '${displayName}', photoURL: '${photoURL}'}));
}
// 以上のタスクの完了を待機
return Promise.all(tasks).then(function() {
// 一時 Firebase アプリを削除し、デフォルト Firebase アプリにログインし、ポップアップをクローズ
var defaultApp = firebase.initializeApp(config);
Promise.all([
defaultApp.auth().signInWithCustomToken(token),
tempApp.delete()]).then(function() {
window.close(); // 完了!ポップアップをクローズ
});
});
});
</script>`;
}
ポップアップ内でユーザーがデフォルト Firebase アプリにログインすると、認証状態リスナーがメインページを起動し(Firebase では、すべてのタブで認証状態が共有されます)、それですべて完了です。ユーザー プロフィール情報を表示したり、Realtime Database や Firebase Storage を使ったりすることができます。
試してみる
自由に試していてだけるデモアプリを作成しています。
https://instagram-auth.appspot.com/
サンプルはオープンソースです。ご自由に Github のリソースをご覧ください。
https://github.com/firebase/custom-auth-samples
Android と iOS への対応
この記事で紹介したコードはウェブアプリ用です。Instagram 認証を Android や iOS アプリに追加するテクニックはいくつかあります。この投稿では紹介できませんが、今後にご期待ください。
これで完了
他の ID プロバイダ用のサンプルを探している場合や、それを組み込む際に問題が発生している場合は、コメントや
GitHub リポジトリの問題点 でお知らせください。喜んでお手伝いいたします。
Posted by
Khanh LeViet - Developer Relations Team
[この記事は Nicolas Garnier、Developer Programs Engineer による The Firebase Blog の記事 "Authenticate your Firebase users with Instagram " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。]
Nicolas Garnier
Developer Programs Engineer
Firebase Authentication は、4 つのすぐに使える ID プロバイダ をサポートしているため、Google、Facebook、Twitter、GitHub で認証を行うのはとても簡単です。たとえば、ウェブアプリで Firebase ユーザーに Google でログインしてもらうために必要なのは次のコードだけです。
var google = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(google);
ただ、その他の ID プロバイダを使って独自の Firebase アプリにログインしてもらいたい場合もあるかもしれません。たとえば、Instagram API を使ってユーザーが Instagram の写真を共有できるようにするつもりなら、Instagram はすばらしい選択肢になるでしょう。
Firebase にビルトインでサポートされていない ID プロバイダを使うことも可能ですが、それには少しばかりのコードとサーバーが必要になります。ここでは、Instagram によるログインを Firebase ウェブアプリに組み込むために必要な手順について、順に説明します。Instagram はログインに OAuth 2.0 を使っています。そのため、本投稿は LinkedIn のようなその他の OAuth 2.0 ID プロバイダによるログイン機能を組み込む際にも役立つはずです。
設計の概要
Instagram は OAuth 2.0 をサポートしています。これは、アプリの認証や Instagram のユーザー ID を含むユーザーデータにアクセスするための主な方法となります。必要になるのは、ユーザーに OAuth 2.0 認証コードのフローを実行してもらい、アプリへのアクセス権を付与することです。OAuth 2.0 のフローは、次のような流れで実行されます。
まず、ユーザーを Instagram の認証 エンドポイントにリダイレクトする必要があります。エンドポイントでは、初めてアプリにアクセス権を付与する際に、ユーザーに同意を求める画面が表示されます。これは、ポップアップ ウィンドウで実現されています。
アプリの認証が完了すると、ユーザーは認証コードとともに元のドメインにリダイレクトされます。Instagram アプリの認証情報を使うと、サーバー側で認証コード をアクセス トークン と交換できます。Instagram では、認証コードを交換する過程でユーザー ID も返されます(LinkedIn などの他の OAuth 2.0 プロバイダでは、追加のリクエストが必要になる場合もあります)。
Instagram のユーザー情報を取得すると、サーバーで Firebase のカスタム認証トークン を作成できるようになります。ユーザーは、このトークンと signInWithCustomToken メソッドを使ってウェブアプリで Firebase にログインできます。
また、クライアント上で Firebase のプロフィールをアップデートできるように、表示名や写真の URL などの Instagram から取得したユーザー プロフィール情報も渡しています。 - 注: Instagram は、ユーザーのメールアドレスは提供していません。そのため、メールアドレスなしの Firebase アカウントができることになりますが、このこと自体には特に問題はありません。 以上が完了すると、ポップアップが閉じます。これで、ユーザーは Instagram アカウントのプロフィール データつきで Firebase にログインできました。
構築に着手する
では、もう少し詳細に踏み込み、重要な部分の実装方法を見てみましょう。今回は、バックエンドを Node.js で記述します。
Instagram にアプリを登録する
ウェブアプリには、Instagram 認証フローを開始するボタンが必要になります。その前に、OAuth 2.0 に必要なアプリの認証情報を取得できるよう、まず Instagram デベロッパー コンソールにアプリケーションを登録する必要があります。
Instagram アプリの設定で、http://localhost:8080/instagram-callback(テスト用)と https:///instagram-callback (本番用ドメイン)を有効なリダイレクト URI としてホワイトリスト登録しておきます。次に、Instagram クライアント ID とクライアント シークレット をメモします。後でこれが必要になります。
アプリを登録し、コールバック URL をホワイトリストに登録すると、クライアント ID とクライアント シークレットが返されます。この流れは、どの OAuth 2.0 プロバイダでも必要となる典型的な手順です。
Instagram の OAuth 2.0 を設定する
サーバーでは、OAuth 2.0 プロトコルの詳細を隠蔽してくれる simple-oauth2 パッケージを使います。これを設定するためには、Instagram クライアント ID とクライアント シークレット、Instagram の OAuth 2.0 トークンと認証エンドポイントなど、いくつかの値を設定する必要があります。Instagram に対して使う必要があるのは、次の値です。
// Instagram OAuth 2 の設定
const credentials = {
client: {
id:YOUR_INSTAGRAM_CLIENT_ID, // 要変更
secret:YOUR_INSTAGRAM_CLIENT_SECRET, // 要変更
},
auth: {
tokenHost: 'https://api.instagram.com',
tokenPath: '/oauth/access_token'
}
};
const oauth2 = require('simple-oauth2').create(credentials);
Instagram 認証フローを開始する
ユーザーを Instagram の同意画面にリダイレクトする URL ハンドラをサーバーに追加します。その際に、Instagram 認証フローを完了したときにユーザーがリダイレクトされて戻ってくる場所となるリダイレクト URI を提供する必要があります。今回は、/instagram-callback をコールバック ハンドラのパスに使用します。
app.get('/redirect', (req, res) => {
// ランダムな状態検証 Cookie を生成
const state = req.cookies.state || crypto.randomBytes(20).toString('hex');
// localhost で安全でない Cookie を許可
const secureCookie = req.get('host').indexOf('localhost:') !== 0;
res.cookie('state', state.toString(), {maxAge: 3600000, secure: secureCookie, httpOnly: true});
const redirectUri = oauth2.authorizationCode.authorizeURL({
redirect_uri: `${req.protocol}://${req.get('host')}/instagram-callback`,
scope: 'basic',
state: state
});
res.redirect(redirectUri);
});
また、セッション固定攻撃 を避けるため、OAuth リクエストの state パラメータでランダムな文字列を渡し、それを HTTP Cookie として保存しています。これによって、戻された state パラメータと Cookie に保存されている値を比較することができ、アプリがフローを開始したことを確認できます。
クライアントでは、ポップアップを表示するボタンとして、次のようなコードを書きます。
function onSignInButtonClick() {
// ポップアップで Auth フローをオープン
window.open('/redirect', 'firebaseAuth', 'height=315,width=400');
};
ユーザーがログインボタンをクリックすると、ポップアップがオープンし、ユーザーが Instagram の同意画面にリダイレクトされます。
同意したユーザーには URL の code クエリ パラメータに認証コードが渡され、先ほど渡した state の値と合わせて、/instagram-callback URL ハンドラにリダイレクトされます。
アクセス トークン用の認証コードの交換
ユーザーがコールバック URL にリダイレクトされた際には、以下の処理を行います。
state Cookie が URL の state クエリ パラメータと同じであることを確認
アクセス トークン用の認証コードを交換し、Instagram からユーザー ID を取得
app.get('/instagram-callback',(req, res) => {
// state Cookie を受け取ったことを確認
if (!req.cookies.state) {
res.status(400).send('State cookie not set or expired.Maybe you took too long to authorize.Please try again.');
// state Cookie が state パラメータと一致することを確認
} else if (req.cookies.state !== req.query.state) {
res.status(400).send('State validation failed');
}
// アクセス トークン用に認証コードを交換
oauth2.authorizationCode.getToken({
code: req.query.code,
redirect_uri: `${req.protocol}://${req.get('host')}/instagram-callback`
}).then(results => {
// Instagram アクセス トークンとユーザー ID の取得が完了
const accessToken = results.access_token;
const instagramUserID = results.user.id;
const profilePic = results.user.profile_picture;
const userName = results.user.full_name;
// ...
});
});
これでこの実装での OAuth 2.0 固有部分は完成です。以降は、ほぼ Firebase に固有の部分です。
次は、Firebase カスタム認証トークンを作成します。そのカスタム認証トークンを使ってログインを実行し、Firebase のユーザー プロフィールをアップデート(詳細は後述)する HTML ページを提供します。
app.get('/instagram-callback', (req, res) => {
// ...
}).then(results => {
// Instagram アクセス トークンとユーザー ID の取得が完了
const accessToken = results.access_token;
const instagramUserID = results.user.id;
const profilePic = results.user.profile_picture;
const userName = results.user.full_name;
// Firebase カスタム認証トークンの作成
const firebaseToken = createFirebaseToken(instagramUserID);
// ログインを実行し、ユーザー プロフィールをアップデートする HTML ページの提供
res.send(
signInFirebaseTemplate(firebaseToken, userName, profilePic, accessToken)));
});
});
カスタム認証トークンの作成
Firebase カスタム認証トークンを作成するには、Firebase にサービス アカウント認証情報 を設定する必要があります。これは、このようなトークンを生成する際に必要となる管理権限を付与するために必要になります。サービス アカウント認証情報ファイルは、service-account.json として保存してください。
const firebase = require('firebase');
const serviceAccount = require('./service-account.json');
firebase.initializeApp({
serviceAccount: serviceAccount
});
カスタム認証トークンの作成はシンプルです。Instagram のユーザー ID に基づいて uid を選択するだけで構いません。
function createFirebaseToken(instagramID) {
// ユーザーに割り当てる uid
const uid = `instagram:${instagramID}`;
// カスタム トークンの作成
return firebase.auth().createCustomToken(uid);
}
注: サービス アカウント認証情報は安全に保管する必要があるため、カスタム トークンの作成は必ずサーバー側で行います。
カスタム トークンを作成した後は、それをクライアントに渡して Firebase にログインします。
カスタム トークンによるログイン
この時点で、サーバーはポップアップ ウィンドウ内で実行される HTML ページを提供し、以下の処理を行います。
後ほど Instagram API にアクセスする場合に備え、Instagram アクセス トークンを Realtime Database に保存(注: 対象のユーザーのみが読み取れるようにセキュリティ ルールを使用します)
Firebase ユーザー名とプロフィール写真のアップデート
ログインの実行とポップアップのクローズ
ここでポイントとなるのは、プロフィールをアップデートする際にデフォルトの Firebase アプリを使うのではなく、一時的な Firebase App インスタンスを使っている点です。これによって、ユーザー プロフィールがアップデートされる前にメインページの Auth リスナーが呼ばれることを防いでいます。
app.get('/instagram-callback', (req, res) => {
// ...
// ログインを実行し、ユーザー プロフィールをアップデートする HTML ページの提供
res.send(
signInFirebaseTemplate(firebaseToken, userName, profilePic, accessToken)));
});
});
function signInFirebaseTemplate(token, displayName, photoURL, instagramAccessToken) {
return `
<script src="https://www.gstatic.com/firebasejs/3.4.0/firebase.js"></script>
<script src="promise.min.js"></script><!-- Promise Polyfill for older browsers -->
<script>
var token = '${token}';
var config = {
apiKey:MY_FIREBASE_API_KEY, // 要変更
databaseURL:MY_DATABASE_URL // 要変更
};
// 一時 Firebase アプリにログインしてプロフィールをアップデート
var tempApp = firebase.initializeApp(config, '_temp_');
tempApp.auth().signInWithCustomToken(token).then(function(user) {
// Realtime Database に Instagram API アクセス トークンを保存
const tasks = [tempApp.database().ref('/instagramAccessToken/' + user.uid)
.set('${instagramAccessToken}')];
// 必要に応じて displayname と photoURL をアップデート
if ('${displayName}' !== user.displayName || '${photoURL}' !== user.photoURL) {
tasks.push(user.updateProfile({displayName: '${displayName}', photoURL: '${photoURL}'}));
}
// 以上のタスクの完了を待機
return Promise.all(tasks).then(function() {
// 一時 Firebase アプリを削除し、デフォルト Firebase アプリにログインし、ポップアップをクローズ
var defaultApp = firebase.initializeApp(config);
Promise.all([
defaultApp.auth().signInWithCustomToken(token),
tempApp.delete()]).then(function() {
window.close(); // 完了!ポップアップをクローズ
});
});
});
</script>`;
}
ポップアップ内でユーザーがデフォルト Firebase アプリにログインすると、認証状態リスナーがメインページを起動し(Firebase では、すべてのタブで認証状態が共有されます)、それですべて完了です。ユーザー プロフィール情報を表示したり、Realtime Database や Firebase Storage を使ったりすることができます。
試してみる
自由に試していてだけるデモアプリを作成しています。https://instagram-auth.appspot.com/
サンプルはオープンソースです。ご自由に Github のリソースをご覧ください。 https://github.com/firebase/custom-auth-samples
Android と iOS への対応
この記事で紹介したコードはウェブアプリ用です。Instagram 認証を Android や iOS アプリに追加するテクニックはいくつかあります。この投稿では紹介できませんが、今後にご期待ください。
これで完了
他の ID プロバイダ用のサンプルを探している場合や、それを組み込む際に問題が発生している場合は、コメントや GitHub リポジトリの問題点 でお知らせください。喜んでお手伝いいたします。
Posted by Khanh LeViet - Developer Relations Team
Todd Kerpelman
Developer Advocate
Firebase Remote Config の機能の中で特に気に入っているのが、異なるユーザー グループに異なるコンテンツを提供できる機能です。たとえば、アプリ上で課金サービスを多く利用しているユーザーには、異なるフロントページを表示したり、フィットネスアプリであれば、ランナーにはあるコンテンツを、ウェイトリフターには別のコンテンツを強調して表示させることができます。
Remote Config が初めてリリースされてから、異なる Firebase Analytics
Audience に属するユーザーにそれぞれ異なるコンテンツを提供することにより、極めて高度なユーザー ターゲティングを実現できるようになりました。
そして最近、異なるユーザー プロパティを有するユーザーに、異なるデータセットを送信する機能が追加されたことで、Remote Config はますます便利になりました。
「ちょっと待って。今の時点でも Audience を使って、ユーザー プロパティだけを使ったターゲティングよりも高度なターゲティングができているのに、なぜこの機能がターゲティングの向上につながるの?」と思われたかもしれません。
確かに、Audience を使えば多数のイベントやユーザー プロパティごとに異なるユーザー グループを定義できるので、特定のグループを対象にした効果的なユーザー ターゲティングが可能です。たとえば、「このゲームのレベル 5 をクリアした、左利きのカナダ人」という Audience を作成できます。
1
しかし、Audience には以下の 2 つの制限があり、これにより Remote Config 内での利用が難しくなっています。
1 つ目は、利用できる Audience が 50 に制限されていること、そして Audience が、Analytics レポートの作成や、Google AdWords を使ったリマーケティング キャンペーンの構築といった、Firebase 内の他のターゲティング機能の Audience と共有されることです。これにより、小規模のアドホックなユーザー グループの作成や、社内で他の人が使っている既存の Audience の編集が難しくなります。
2 つ目は、現在の Audience では、一度ユーザーが Audience に入ると、そこから抜けられないということです。Audience をリマーケティング キャンペーンに活用する、一般的なマーケティング担当者にとってはまったく問題もないですが、Remote Config で利用する場合は、これが問題となることがあります。
たとえば、ゲームは開始したけれど、レベル 10 をクリアしていないユーザーに対し「新人」という Audience を作成したいとします。このグループから Audience を作成しようとした場合、ゲームを開始したユーザー全員が、その Audience に入ることになります。しかし、レベル 15 や 20 をクリアした後も、ユーザーはこの Audience に入ったままとなるため、実質この Audience が「すべてのユーザー」という Audience になってしまいます。
このため、Remote Configでのユーザー ターゲティングにおいては、ユーザー プロパティを使ってコンテンツをカスタマイズする方が便利です。ユーザー プロパティを使うと、より小さなワンオフのターゲット グループを多数作成でき、従来の Audience よりもダイナミックなターゲティングが可能になります。
たとえば、フィットネス アプリにおいて、ユーザーの好きなエクササイズのタイプによって、異なるフロントページを表示させたいとします。この場合、ユーザーの好きなエクササイズをユーザー プロパティに保存し、このプロパティを基にして新たな条件を作成することで、簡単にフロントページの設定が可能です。
そして、それぞれの条件を基に、異なるフロントページを表示させることができるようになります。
このように、複数の異なる条件を簡単に作成でき、Firebase Analytics Audience の「上限」を気にする必要もなくなります。
2
別の例として、ゲームアプリにおいて、ユーザーのレベルによりゲームのビヘイビアを変更したいとします。この場合、ユーザーのレベルをユーザー プロパティに保存し、階層別に異なる条件を多数作成できます。たとえば、レベル 4 からレベル 10 のユーザーを「中間」層として作成し、毎日異なるボーナスを該当するプレイヤーに付与することができます。
ユーザーがレベルを上げていくにしたがって、ユーザー プロパティによって、ユーザーが違う階層に配置されるので、その階層に基づき、Remote Config が自動的に対応する値を付与します。それぞれに対し、新しい Audience を作成する必要はありません。
後でグループ分けを変更したい場合も、簡単に実施できます。もし、中間層をレベル 6 からスタートさせたいと考えた場合、Firebase のコントロール パネル上で変更可能であり、変更は Remote Config に即座に反映されます。
また、さらに別のプレイヤーグループに対して、ゲームのビヘイビアを変更しようと突然思い立った場合でも、4番目の階層を簡単に追加できます。
ユーザー プロパティは、通常文字列で保存されているので、「完全一致」や「を含む」などの文字列比較を実行することができます。ユーザーの好きな 3 つのエクササイズを文字列(たとえば、
yoga|interval_training|running )で保存している場合、エクササイズに「running」を含むユーザーをターゲットにすることで、「すべてのランナー」という条件を作成できます。
また、上記のプレイヤー階層で説明したように、数字による比較を実行することも可能です。Remote Config は、たとえば、
"42" を 42 に、
"3.14159" を 3.14159 にといったように、既知の方法で文字列を数値に変換します。ただし、整数や浮動小数に変換されないユーザー プロパティの文字列(たとえば、
"Level_42" )との数値比較は、エラーとなります。
ユーザープロパティを使ったターゲティングは比較的新しい機能なので、まだ利用されたことがない場合はぜひ試してみてください。
Remote Config Panel に移動し、既に保存しているユーザー プロパティを使って、まずはちょっとした変更を実施してみましょう。正常に動作した場合、自分のアプリのどの部分をカスタマイズするのが効果的かを考え、それをサポートするためのユーザー プロパティを追加してみてください。
もし、うまく活用することができたら、ぜひ
お知らせください 。フィードバックを楽しみにしています。
1 「利き手」をユーザー プロパティとしてターゲティングしたと仮定。
2 正確に言えば、Remote Config の条件作成数にも 100 という上限がありますが、50 に比べればさまざまな実験をする余地があります。
Posted by
Yoshifumi Yamaguchi - Developer Relations Team
[この記事は Todd Kerpelman 、デベロッパー アドボケートによる The Firebase Blog の記事 "Better User Targeting with Firebase Remote Config " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。]
Todd Kerpelman
Developer Advocate
Firebase Remote Config の機能の中で特に気に入っているのが、異なるユーザー グループに異なるコンテンツを提供できる機能です。たとえば、アプリ上で課金サービスを多く利用しているユーザーには、異なるフロントページを表示したり、フィットネスアプリであれば、ランナーにはあるコンテンツを、ウェイトリフターには別のコンテンツを強調して表示させることができます。
Remote Config が初めてリリースされてから、異なる Firebase Analytics Audience に属するユーザーにそれぞれ異なるコンテンツを提供することにより、極めて高度なユーザー ターゲティングを実現できるようになりました。
そして最近、異なるユーザー プロパティを有するユーザーに、異なるデータセットを送信する機能が追加されたことで、Remote Config はますます便利になりました。
「ちょっと待って。今の時点でも Audience を使って、ユーザー プロパティだけを使ったターゲティングよりも高度なターゲティングができているのに、なぜこの機能がターゲティングの向上につながるの?」と思われたかもしれません。
確かに、Audience を使えば多数のイベントやユーザー プロパティごとに異なるユーザー グループを定義できるので、特定のグループを対象にした効果的なユーザー ターゲティングが可能です。たとえば、「このゲームのレベル 5 をクリアした、左利きのカナダ人」という Audience を作成できます。1
しかし、Audience には以下の 2 つの制限があり、これにより Remote Config 内での利用が難しくなっています。
1 つ目は、利用できる Audience が 50 に制限されていること、そして Audience が、Analytics レポートの作成や、Google AdWords を使ったリマーケティング キャンペーンの構築といった、Firebase 内の他のターゲティング機能の Audience と共有されることです。これにより、小規模のアドホックなユーザー グループの作成や、社内で他の人が使っている既存の Audience の編集が難しくなります。
2 つ目は、現在の Audience では、一度ユーザーが Audience に入ると、そこから抜けられないということです。Audience をリマーケティング キャンペーンに活用する、一般的なマーケティング担当者にとってはまったく問題もないですが、Remote Config で利用する場合は、これが問題となることがあります。
たとえば、ゲームは開始したけれど、レベル 10 をクリアしていないユーザーに対し「新人」という Audience を作成したいとします。このグループから Audience を作成しようとした場合、ゲームを開始したユーザー全員が、その Audience に入ることになります。しかし、レベル 15 や 20 をクリアした後も、ユーザーはこの Audience に入ったままとなるため、実質この Audience が「すべてのユーザー」という Audience になってしまいます。
このため、Remote Configでのユーザー ターゲティングにおいては、ユーザー プロパティを使ってコンテンツをカスタマイズする方が便利です。ユーザー プロパティを使うと、より小さなワンオフのターゲット グループを多数作成でき、従来の Audience よりもダイナミックなターゲティングが可能になります。
たとえば、フィットネス アプリにおいて、ユーザーの好きなエクササイズのタイプによって、異なるフロントページを表示させたいとします。この場合、ユーザーの好きなエクササイズをユーザー プロパティに保存し、このプロパティを基にして新たな条件を作成することで、簡単にフロントページの設定が可能です。
そして、それぞれの条件を基に、異なるフロントページを表示させることができるようになります。
このように、複数の異なる条件を簡単に作成でき、Firebase Analytics Audience の「上限」を気にする必要もなくなります。2
別の例として、ゲームアプリにおいて、ユーザーのレベルによりゲームのビヘイビアを変更したいとします。この場合、ユーザーのレベルをユーザー プロパティに保存し、階層別に異なる条件を多数作成できます。たとえば、レベル 4 からレベル 10 のユーザーを「中間」層として作成し、毎日異なるボーナスを該当するプレイヤーに付与することができます。
ユーザーがレベルを上げていくにしたがって、ユーザー プロパティによって、ユーザーが違う階層に配置されるので、その階層に基づき、Remote Config が自動的に対応する値を付与します。それぞれに対し、新しい Audience を作成する必要はありません。
後でグループ分けを変更したい場合も、簡単に実施できます。もし、中間層をレベル 6 からスタートさせたいと考えた場合、Firebase のコントロール パネル上で変更可能であり、変更は Remote Config に即座に反映されます。
また、さらに別のプレイヤーグループに対して、ゲームのビヘイビアを変更しようと突然思い立った場合でも、4番目の階層を簡単に追加できます。
ユーザー プロパティは、通常文字列で保存されているので、「完全一致」や「を含む」などの文字列比較を実行することができます。ユーザーの好きな 3 つのエクササイズを文字列(たとえば、yoga|interval_training|running )で保存している場合、エクササイズに「running」を含むユーザーをターゲットにすることで、「すべてのランナー」という条件を作成できます。
また、上記のプレイヤー階層で説明したように、数字による比較を実行することも可能です。Remote Config は、たとえば、"42" を 42 に、 "3.14159" を 3.14159 にといったように、既知の方法で文字列を数値に変換します。ただし、整数や浮動小数に変換されないユーザー プロパティの文字列(たとえば、"Level_42" )との数値比較は、エラーとなります。
ユーザープロパティを使ったターゲティングは比較的新しい機能なので、まだ利用されたことがない場合はぜひ試してみてください。Remote Config Panel に移動し、既に保存しているユーザー プロパティを使って、まずはちょっとした変更を実施してみましょう。正常に動作した場合、自分のアプリのどの部分をカスタマイズするのが効果的かを考え、それをサポートするためのユーザー プロパティを追加してみてください。
もし、うまく活用することができたら、ぜひお知らせください 。フィードバックを楽しみにしています。
1 「利き手」をユーザー プロパティとしてターゲティングしたと仮定。
2 正確に言えば、Remote Config の条件作成数にも 100 という上限がありますが、50 に比べればさまざまな実験をする余地があります。
Posted by Yoshifumi Yamaguchi - Developer Relations Team
Todd Kerpelman
Developer Advocate
一部のグループの人たちの間でプライベートな会話ができるチャットアプリがあるとしましょう。または、友人たちのグループで一緒にアルバムを作ることができる写真共有アプリでも構いません。こういった種類のデータ共有を小さなグループのユーザーに制限し、世界に向けて公開しないようにするにはどうすればよいでしょうか。
Firebase のセキュリティ ルールが活躍するのはこのような場合です。Firebase のセキュリティ ルールはとても強力ですが、ちょっとしたガイダンスが必要になる場合もあります。それはルールが複雑であるからではなく、ほとんどの人々は十分な知見を得られるほど頻繁にこの機能を使っていないからです。
皆さんはラッキーです。なぜかと言えば、私はこのブログ投稿を書くために、ここ数週間 Firebase セキュリティの専門家の人々の隣にいてずいぶん彼らを悩ませてきたからです。さらに重要なのは、セキュリティ ルールを理解しやすくするある奇策を見つけたことです。これについては、本投稿の最後の部分で皆さんにお話しします。
ここでは、プライベート グループで会話を行うチャットアプリの例について考えてみましょう。チャット グループに参加しているユーザーは全員チャット メッセージを読み書きできますが、その他のユーザーには見られたくありません。
Database の構造は次のようになっているものとします。もちろん、これを実現する方法はたくさんありますが、デモ用の構造として一番簡単なのはおそらくこのようなものでしょう。
準プライベートと言える各チャットの中には、参加できるユーザーの一覧とチャット メッセージの一覧があります(現実の世界では、これらのユーザー ID は user_abc よりははるかに複雑になるでしょう)。
ここで設定したい最初のセキュリティ ルールは、メンバーのリストにあるユーザーのみがチャット メッセージを参照できるようにすることです。これは、いくつかのセキュリティ ルールを使って作成できます。
{
"rules": {
"chats": {
"$chatID": {
"messages": {
".read": "data.parent().child('members').child(auth.uid).exists()"
}
}
}
}
}
ここで言っているのは、
chats//messages 内のチャットは、同じチャットの
members セクションに
userID が存在する場合に限り読み取りが許可されているということです。
ところで、
$chatID という行は気にならないでしょうか。これはワイルドカードと同じく何にでもマッチしますが、マッチした値を後で使えるように
$chatID 変数に格納することを示します。
では、
user_abc の場合はどうなるでしょうか。チャット メッセージは完全に読み取りが可能です。しかし、
user_xyz は読み取りが許可されていません。チャット グループに
members/user_xyz というエントリがないからです。
このことがわかれば、同じようにしてメンバーのみがチャット メッセージを書き込めるルールを追加するのも簡単です。
"chats": {
"$chatID": {
"messages": {
".read": "data.parent().child('members').child(auth.uid).exists()",
".write": "data.parent().child('members').child(auth.uid).exists()"
}
}
}
お望みであれば、さらに細かく制御することも可能です。このチャットアプリに、参照はできてもメッセージを書き込むことはできない
"lurker" というユーザータイプが存在する場合はどうなるでしょうか。
これにも対応することができます。その場合、「owner または chatter のみメッセージの書き込みが可能」ということを示すルールに変更します。これは、次のようなルールになるでしょう。
"chats": {
"$chatID": {
"messages": {
".read": "data.parent().child('members').child(auth.uid).exists()",
".write": "data.parent().child('members').child(auth.uid).val() == 'owner' || data.parent().child('members').child(auth.uid).val()=='chatter'"
}
}
}
(簡潔になるように、以降のサンプルでは "rules" 行を省略します。)
ちなみに、「『lurker でないユーザーのみチャット メッセージを書き込めるようにする』方が簡単ではないか」と思われる方もいらっしゃるかもしれません。その場合、当然ながらコードは 1 行少なくて済みます。
"chats": {
"$chatID": {
"messages": {
".read": "data.parent().child('members').child(auth.uid).exists()",
".write": "data.parent().child('members').child(auth.uid).val() != 'lurker'"
}
}
しかし、セキュリティはブラックリストに基づくよりもホワイトリストに基づく方がよいものです。考えてみてください。アプリに突然新しいクラスのユーザー(たとえば "newbies")を追加し、このルールの更新を忘れてしまった場合はどうなるでしょうか。
最初のルールでは、新しいグループのユーザーは投稿はできません。しかし、2 番目のルールでは、何でも投稿できるようになってしまいます。どちらの場合も意図とは違うので問題にはなりますが、セキュリティの観点からすれば、後者の方がはるかによくない状況です。
もちろん、ここに記載したことはすべて、あるとても小さな問題を見逃しています。そもそも、ユーザーのリストはどのようにして設定したのでしょうか。
しばらくの間は、ユーザーがアプリから友人のリストを取得できたことにしておきましょう(これは、読者向けの練習問題として残しておきます)。グループチャットに新しいユーザーを追加する際に考えられるオプションはいくつかあります。
チャットのユーザーは誰でも他のユーザーを追加できる
チャットの所有者だけが他のユーザーを追加できる
チャットへの参加申請は誰でもできるが、所有者による承認が必要
いずれの案でも問題ないでしょう。アプリにとってどれが最適なユーザー エクスペリエンスであるかを判断するのはアプリのデベロッパーです。
それでは、1 つずつ順番に見ていきましょう。
チャットのユーザーは誰でも他のユーザーを追加できる
最初のオプションに対応するには、「既にメンバーリストに含まれているユーザーはメンバーリストの書き込みが可能」というルールを設定する必要があります。
メンバーリストに対して、既に設定してある投稿用のルールによく似たものを設定します。
"chats": {
"$chatID": {
"messages": {
".read": "data.parent().child('members').child(auth.uid).exists()",
".write": "data.parent().child('members').child(auth.uid).val() == 'owner' || data.parent().child('members').child(auth.uid).val()=='chatter'"
},
"members": {
".read": "data.child(auth.uid).exists()",
".write": "data.child(auth.uid).exists()"
}
}
}
このルールは、基本的に現在のユーザー ID がリスト内にあるユーザーであれば、メンバーリストの読み取りや書き込みができることを示しています。
チャットの所有者だけが他のユーザーを追加できる
所有者だけがリストに書き込めるような制限を加えるのも簡単でしょう。
"chats": {
"$chatID": {
"messages": {
".read": "data.parent().child('members').child(auth.uid).exists()",
".write": "data.parent().child('members').child(auth.uid).val() == 'owner' || data.parent().child('members').child(auth.uid).val()=='chatter'"
},
"members": {
".read": "data.child(auth.uid).val() == 'owner'",
".write": "data.child(auth.uid).val() == 'owner'"
}
}
}
これは、「userID が所有者として登録されていれば、チャットの members 要素に書き込みが可能」という意味になります。
これで 2 番目のケースにも対応できます。
チャットへの参加申請は誰でもできるが、所有者による承認が必要
では、ユーザーが参加申請を行い、所有者が承認する場合はどうでしょう。この場合は、Database に
members のリストだけでなく、ユーザーが追加できる保留中のリストを含めるとよいでしょう。
グループの所有者は、これらの見込みユーザーをメンバーリストに追加したり、保留中のリストから削除できるようにします。
宣言する最初のルールは、「自分のエントリであれば、保留中リストに追加できる」です。つまり、追加する項目のキーは自分のユーザー ID でなければなりません。
これを追加したルールは次のようになります。
"chats": {
"$chatID": {
"messages": {
".read": "data.parent().child('members').child(auth.uid).exists()",
".write": "data.parent().child('members').child(auth.uid).val() == 'owner' || data.parent().child('members').child(auth.uid).val()=='chatter'"
},
"members": {
".read": "data.child(auth.uid).val() == 'owner'",
".write": "data.child(auth.uid).val() == 'owner'"
},
"pending": {
"$uid": {
".write": "$uid === auth.uid"
}
}
}
}
ここで言っているのは、「
pending/ 以下の要素には、uid が自分の
userID であれば書き込みできる」ということです。
さらに厳密にするなら、まだ "pending" リストに追加されていない場合のみ追加可能と指定することもできます。これは次のようになります。
"pending": {
"$uid": {
".write": "$uid === auth.uid && !data.exists()"
}
}
さらに、既にチャットのメンバーである場合は、自分を追加することができないルールを指定してみましょう。これにはあまり意味がないかもしれませんが、最終的に次のようなルールになります。
"pending": {
"$uid": {
".write": "$uid === auth.uid && !data.exists() && !data.parent().parent().child('members').child($uid).exists()"
}
}
次に、所有者は
pending フォルダ全体を読み書きできるというルールを追加します。
"pending": {
".read": "data.parent().child('members').child(auth.uid).val() === 'owner'",
".write": "data.parent().child('members').child(auth.uid).val() === 'owner'",
"$uid": {
".write": "$uid === auth.uid && !data.exists() && !data.parent().parent().child('members').child($uid).exists()"
}
}
これで完成です。チャットの所有者のみメンバーリストを読み書きできるという前のセクションからのルールをそのまま使っていれば、所有者が保留中リストからエントリを削除してメンバーリストに追加できるセキュリティ ルールになります。
最後に 1 つだけ、まだ触れていなかった重要なルールがあります。それは、新しいチャット グループの作成です。チャット グループはどのようにすれば設定できるでしょうか。「メンバーリストが空であれば、誰でもそこに書き込んで自分を所有者とすることができる」ことを宣言すれば、チャット グループを追加することができます。
"members": {
".read": "data.child(auth.uid).val() == 'owner'",
".write": "data.child(auth.uid).val() == 'owner' ||(!data.exists()&&newData.child(auth.uid).val()=='owner')"
}
この書き込みを行うために、まず
/chats/chat_345/members にオブジェクト
{ "user_zzz" : "owner" } を書き込む場合を考えてみてください。newData 行はこのオブジェクトを参照し、ログインユーザー(
user_zzz)のキーの子要素が所有者になっていることを確認しています。
その後、所有者は自由にメッセージやユーザーを追加することができます。所有者としてリストに登録されているため、セキュリティ ルールはこのようなアクションを何の問題もなく許可します。
なお、セキュリティ ルールでは、「ディレクトリ作成」の操作は個別の概念として存在しない点に注意してください。ユーザーが
chat_456/messages/abc への書き込みを許可されている場合、messages が既に存在するかしないかにかかわらずルールが適用されます(
chat_456 についても同じことが言えます)。
セキュリティ ルールの動作を理解する
私は Firebase セキュリティのエキスパートではありませんが、ブログ投稿ではそのふりをすることができます。その秘訣は、ルール シミュレータです。
ルールを変更する際、それを発行する前に Database への読み取りや書き込みをシミュレートしてテストすることができます。Firebase コンソールの Database 配下にある Rules タブの右上には、「SIMULATOR」と書かれるボタンがあります。クリックするとフォームが表示され、そこから任意の種類の読み取り操作や書き込み操作をテストできます。
今回の例では、最後のルールについて、
"user_zzz" としてログインしているユーザーで自分自身を所有者として空の
/chats/chat_987/members リストに追加する操作をテストしてみます。すると、ルール シミュレータにこの操作は許可されている旨が表示され、書き込みアクションが true と評価された場所がハイライト表示されます。
(厳密に言えば、間違った行がハイライト表示されており、実際に true と評価されるのはルールの 13 行目です。おそらく、ハイライト表示は文字列内の改行を細かく認識していないものと思われます)
一方、このユーザーが空でないリストに自身を所有者として追加しようとすると失敗します。これは想定どおりの動作です。
さらなる改善
さらにいくつかの改善を行うことができます。現在の状況では、所有者は他のメンバーを所有者として追加できるようになっています。実際は、これが望ましい場合もそうでない場合もあるでしょう。
考えてみると、新しいメンバーが適切な役割で追加されているかどうかを検証してはいませんでした。また、UI が扱うことができる長さにチャット メッセージが収まるようにする検証ルールもいくつか追加できるでしょう。この点については、ぜひいろいろ試してみてください。
次に示すのは、最終的なルールです。これをご自分のチャットアプリにコピーして貼り付け、どのように改善できるかを考えてみてください。
{
"rules": {
"chats": {
"$chatID": {
"messages": {
".read": "data.parent().child('members').child(auth.uid).exists()",
".write": "data.parent().child('members').child(auth.uid).val() == 'owner' || data.parent().child('members').child(auth.uid).val()=='chatter'"
},
"members": {
".read": "data.child(auth.uid).val() == 'owner'",
".write": "data.child(auth.uid).val() == 'owner' ||(!data.exists()&&newData.child(auth.uid).val()=='owner')"
},
"pending": {
".read": "data.parent().child('members').child(auth.uid).val() === 'owner'",
".write": "data.parent().child('members').child(auth.uid).val() === 'owner'",
"$uid": {
".write": "$uid === auth.uid && !data.exists() && !data.parent().parent().child('members').child($uid).exists()"
}
}
}
}
}
}
さらにヘルプが必要な場合は、ぜひ
ドキュメント をご覧いただき、シミュレータでいろいろ試してみてください。シミュレータで Database のセキュリティ ルールを試してみれば、きっと楽しい 1 週間を過ごすことができるでしょう。少なくとも、最初の 3 日は楽しめます。
Posted by
Khanh LeViet - Developer Relations Team
[この記事は Todd Kerpelman、デベロッパー アドボケートによる The Firebase Blog の記事 "Group Security in the Firebase Database " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。]
Todd Kerpelman
Developer Advocate
一部のグループの人たちの間でプライベートな会話ができるチャットアプリがあるとしましょう。または、友人たちのグループで一緒にアルバムを作ることができる写真共有アプリでも構いません。こういった種類のデータ共有を小さなグループのユーザーに制限し、世界に向けて公開しないようにするにはどうすればよいでしょうか。
Firebase のセキュリティ ルールが活躍するのはこのような場合です。Firebase のセキュリティ ルールはとても強力ですが、ちょっとしたガイダンスが必要になる場合もあります。それはルールが複雑であるからではなく、ほとんどの人々は十分な知見を得られるほど頻繁にこの機能を使っていないからです。
皆さんはラッキーです。なぜかと言えば、私はこのブログ投稿を書くために、ここ数週間 Firebase セキュリティの専門家の人々の隣にいてずいぶん彼らを悩ませてきたからです。さらに重要なのは、セキュリティ ルールを理解しやすくするある奇策を見つけたことです。これについては、本投稿の最後の部分で皆さんにお話しします。
ここでは、プライベート グループで会話を行うチャットアプリの例について考えてみましょう。チャット グループに参加しているユーザーは全員チャット メッセージを読み書きできますが、その他のユーザーには見られたくありません。
Database の構造は次のようになっているものとします。もちろん、これを実現する方法はたくさんありますが、デモ用の構造として一番簡単なのはおそらくこのようなものでしょう。
準プライベートと言える各チャットの中には、参加できるユーザーの一覧とチャット メッセージの一覧があります(現実の世界では、これらのユーザー ID は user_abc よりははるかに複雑になるでしょう)。
ここで設定したい最初のセキュリティ ルールは、メンバーのリストにあるユーザーのみがチャット メッセージを参照できるようにすることです。これは、いくつかのセキュリティ ルールを使って作成できます。
{
"rules": {
"chats": {
"$chatID": {
"messages": {
".read": "data.parent().child('members').child(auth.uid).exists()"
}
}
}
}
}
ここで言っているのは、chats//messages 内のチャットは、同じチャットの members セクションに userID が存在する場合に限り読み取りが許可されているということです。
ところで、$chatID という行は気にならないでしょうか。これはワイルドカードと同じく何にでもマッチしますが、マッチした値を後で使えるように $chatID 変数に格納することを示します。
では、user_abc の場合はどうなるでしょうか。チャット メッセージは完全に読み取りが可能です。しかし、user_xyz は読み取りが許可されていません。チャット グループに members/user_xyz というエントリがないからです。
このことがわかれば、同じようにしてメンバーのみがチャット メッセージを書き込めるルールを追加するのも簡単です。
"chats": {
"$chatID": {
"messages": {
".read": "data.parent().child('members').child(auth.uid).exists()",
".write": "data.parent().child('members').child(auth.uid).exists()"
}
}
}
お望みであれば、さらに細かく制御することも可能です。このチャットアプリに、参照はできてもメッセージを書き込むことはできない "lurker" というユーザータイプが存在する場合はどうなるでしょうか。
これにも対応することができます。その場合、「owner または chatter のみメッセージの書き込みが可能」ということを示すルールに変更します。これは、次のようなルールになるでしょう。
"chats": {
"$chatID": {
"messages": {
".read": "data.parent().child('members').child(auth.uid).exists()",
".write": "data.parent().child('members').child(auth.uid).val() == 'owner' || data.parent().child('members').child(auth.uid).val()=='chatter'"
}
}
}
(簡潔になるように、以降のサンプルでは "rules" 行を省略します。)
ちなみに、「『lurker でないユーザーのみチャット メッセージを書き込めるようにする』方が簡単ではないか」と思われる方もいらっしゃるかもしれません。その場合、当然ながらコードは 1 行少なくて済みます。
"chats": {
"$chatID": {
"messages": {
".read": "data.parent().child('members').child(auth.uid).exists()",
".write": "data.parent().child('members').child(auth.uid).val() != 'lurker'"
}
}
しかし、セキュリティはブラックリストに基づくよりもホワイトリストに基づく方がよいものです。考えてみてください。アプリに突然新しいクラスのユーザー(たとえば "newbies")を追加し、このルールの更新を忘れてしまった場合はどうなるでしょうか。
最初のルールでは、新しいグループのユーザーは投稿はできません。しかし、2 番目のルールでは、何でも投稿できるようになってしまいます。どちらの場合も意図とは違うので問題にはなりますが、セキュリティの観点からすれば、後者の方がはるかによくない状況です。
もちろん、ここに記載したことはすべて、あるとても小さな問題を見逃しています。そもそも、ユーザーのリストはどのようにして設定したのでしょうか。
しばらくの間は、ユーザーがアプリから友人のリストを取得できたことにしておきましょう(これは、読者向けの練習問題として残しておきます)。グループチャットに新しいユーザーを追加する際に考えられるオプションはいくつかあります。
チャットのユーザーは誰でも他のユーザーを追加できる
チャットの所有者だけが他のユーザーを追加できる
チャットへの参加申請は誰でもできるが、所有者による承認が必要
いずれの案でも問題ないでしょう。アプリにとってどれが最適なユーザー エクスペリエンスであるかを判断するのはアプリのデベロッパーです。
それでは、1 つずつ順番に見ていきましょう。
チャットのユーザーは誰でも他のユーザーを追加できる
最初のオプションに対応するには、「既にメンバーリストに含まれているユーザーはメンバーリストの書き込みが可能」というルールを設定する必要があります。
メンバーリストに対して、既に設定してある投稿用のルールによく似たものを設定します。
"chats": {
"$chatID": {
"messages": {
".read": "data.parent().child('members').child(auth.uid).exists()",
".write": "data.parent().child('members').child(auth.uid).val() == 'owner' || data.parent().child('members').child(auth.uid).val()=='chatter'"
},
"members": {
".read": "data.child(auth.uid).exists()",
".write": "data.child(auth.uid).exists()"
}
}
}
このルールは、基本的に現在のユーザー ID がリスト内にあるユーザーであれば、メンバーリストの読み取りや書き込みができることを示しています。
チャットの所有者だけが他のユーザーを追加できる
所有者だけがリストに書き込めるような制限を加えるのも簡単でしょう。
"chats": {
"$chatID": {
"messages": {
".read": "data.parent().child('members').child(auth.uid).exists()",
".write": "data.parent().child('members').child(auth.uid).val() == 'owner' || data.parent().child('members').child(auth.uid).val()=='chatter'"
},
"members": {
".read": "data.child(auth.uid).val() == 'owner'",
".write": "data.child(auth.uid).val() == 'owner'"
}
}
}
これは、「userID が所有者として登録されていれば、チャットの members 要素に書き込みが可能」という意味になります。
これで 2 番目のケースにも対応できます。
チャットへの参加申請は誰でもできるが、所有者による承認が必要
では、ユーザーが参加申請を行い、所有者が承認する場合はどうでしょう。この場合は、Database に members のリストだけでなく、ユーザーが追加できる保留中のリストを含めるとよいでしょう。
グループの所有者は、これらの見込みユーザーをメンバーリストに追加したり、保留中のリストから削除できるようにします。
宣言する最初のルールは、「自分のエントリであれば、保留中リストに追加できる」です。つまり、追加する項目のキーは自分のユーザー ID でなければなりません。
これを追加したルールは次のようになります。
"chats": {
"$chatID": {
"messages": {
".read": "data.parent().child('members').child(auth.uid).exists()",
".write": "data.parent().child('members').child(auth.uid).val() == 'owner' || data.parent().child('members').child(auth.uid).val()=='chatter'"
},
"members": {
".read": "data.child(auth.uid).val() == 'owner'",
".write": "data.child(auth.uid).val() == 'owner'"
},
"pending": {
"$uid": {
".write": "$uid === auth.uid"
}
}
}
}
ここで言っているのは、「pending/ 以下の要素には、uid が自分の userID であれば書き込みできる」ということです。
さらに厳密にするなら、まだ "pending" リストに追加されていない場合のみ追加可能と指定することもできます。これは次のようになります。
"pending": {
"$uid": {
".write": "$uid === auth.uid && !data.exists()"
}
}
さらに、既にチャットのメンバーである場合は、自分を追加することができないルールを指定してみましょう。これにはあまり意味がないかもしれませんが、最終的に次のようなルールになります。
"pending": {
"$uid": {
".write": "$uid === auth.uid && !data.exists() && !data.parent().parent().child('members').child($uid).exists()"
}
}
次に、所有者は pending フォルダ全体を読み書きできるというルールを追加します。
"pending": {
".read": "data.parent().child('members').child(auth.uid).val() === 'owner'",
".write": "data.parent().child('members').child(auth.uid).val() === 'owner'",
"$uid": {
".write": "$uid === auth.uid && !data.exists() && !data.parent().parent().child('members').child($uid).exists()"
}
}
これで完成です。チャットの所有者のみメンバーリストを読み書きできるという前のセクションからのルールをそのまま使っていれば、所有者が保留中リストからエントリを削除してメンバーリストに追加できるセキュリティ ルールになります。
最後に 1 つだけ、まだ触れていなかった重要なルールがあります。それは、新しいチャット グループの作成です。チャット グループはどのようにすれば設定できるでしょうか。「メンバーリストが空であれば、誰でもそこに書き込んで自分を所有者とすることができる」ことを宣言すれば、チャット グループを追加することができます。
"members": {
".read": "data.child(auth.uid).val() == 'owner'",
".write": "data.child(auth.uid).val() == 'owner' ||(!data.exists()&&newData.child(auth.uid).val()=='owner')"
}
この書き込みを行うために、まず /chats/chat_345/members にオブジェクト { "user_zzz" : "owner" } を書き込む場合を考えてみてください。newData 行はこのオブジェクトを参照し、ログインユーザー(user_zzz)のキーの子要素が所有者になっていることを確認しています。
その後、所有者は自由にメッセージやユーザーを追加することができます。所有者としてリストに登録されているため、セキュリティ ルールはこのようなアクションを何の問題もなく許可します。
なお、セキュリティ ルールでは、「ディレクトリ作成」の操作は個別の概念として存在しない点に注意してください。ユーザーが chat_456/messages/abc への書き込みを許可されている場合、messages が既に存在するかしないかにかかわらずルールが適用されます(chat_456 についても同じことが言えます)。
セキュリティ ルールの動作を理解する
私は Firebase セキュリティのエキスパートではありませんが、ブログ投稿ではそのふりをすることができます。その秘訣は、ルール シミュレータです。
ルールを変更する際、それを発行する前に Database への読み取りや書き込みをシミュレートしてテストすることができます。Firebase コンソールの Database 配下にある Rules タブの右上には、「SIMULATOR」と書かれるボタンがあります。クリックするとフォームが表示され、そこから任意の種類の読み取り操作や書き込み操作をテストできます。
今回の例では、最後のルールについて、"user_zzz" としてログインしているユーザーで自分自身を所有者として空の /chats/chat_987/members リストに追加する操作をテストしてみます。すると、ルール シミュレータにこの操作は許可されている旨が表示され、書き込みアクションが true と評価された場所がハイライト表示されます。
(厳密に言えば、間違った行がハイライト表示されており、実際に true と評価されるのはルールの 13 行目です。おそらく、ハイライト表示は文字列内の改行を細かく認識していないものと思われます)
一方、このユーザーが空でないリストに自身を所有者として追加しようとすると失敗します。これは想定どおりの動作です。
さらなる改善
さらにいくつかの改善を行うことができます。現在の状況では、所有者は他のメンバーを所有者として追加できるようになっています。実際は、これが望ましい場合もそうでない場合もあるでしょう。
考えてみると、新しいメンバーが適切な役割で追加されているかどうかを検証してはいませんでした。また、UI が扱うことができる長さにチャット メッセージが収まるようにする検証ルールもいくつか追加できるでしょう。この点については、ぜひいろいろ試してみてください。
次に示すのは、最終的なルールです。これをご自分のチャットアプリにコピーして貼り付け、どのように改善できるかを考えてみてください。
{
"rules": {
"chats": {
"$chatID": {
"messages": {
".read": "data.parent().child('members').child(auth.uid).exists()",
".write": "data.parent().child('members').child(auth.uid).val() == 'owner' || data.parent().child('members').child(auth.uid).val()=='chatter'"
},
"members": {
".read": "data.child(auth.uid).val() == 'owner'",
".write": "data.child(auth.uid).val() == 'owner' ||(!data.exists()&&newData.child(auth.uid).val()=='owner')"
},
"pending": {
".read": "data.parent().child('members').child(auth.uid).val() === 'owner'",
".write": "data.parent().child('members').child(auth.uid).val() === 'owner'",
"$uid": {
".write": "$uid === auth.uid && !data.exists() && !data.parent().parent().child('members').child($uid).exists()"
}
}
}
}
}
}
さらにヘルプが必要な場合は、ぜひドキュメント をご覧いただき、シミュレータでいろいろ試してみてください。シミュレータで Database のセキュリティ ルールを試してみれば、きっと楽しい 1 週間を過ごすことができるでしょう。少なくとも、最初の 3 日は楽しめます。
Posted by Khanh LeViet - Developer Relations Team