developers

Firebase および Firestore でリアルタむプアプリを䜜る

Firebase および Firestore でリアルタむム Web チャットを䜜る方法を孊びたしょう

Nov 7, 2018 • 8 min read

TL;DR 本曞では、Firebase および Firestore でリアルタむム Web チャットを䜜る方法を孊びたす。さらに、認蚌システムずしお Firebase アプリで Auth0 を䜿う方法そしおそれを䜿甚する理由も孊びたす。これらテクノロゞヌず統合の手始めずしお、確実にメッセヌゞを Firestore に保存しFirebase が提䟛するリアルタむムのデヌタベヌス、ナヌザヌが Auth0 を通しお認蚌できる簡単なリアルタむム Web チャットを構築したす。必芁であれば、この GitHub リポゞトリにある蚘事で䜜成した最埌のコヌドをご芧ください。

“Firebase および Firestore でリアルタむム Web チャットを䜜る方法を孊びたしょう。”

これをツむヌトする

前提条件

以䞋のステップに埓うには、Node.js および JavaScript に぀いおの基本的な理解を深めおください。サヌバヌ偎には Node.js を、プロゞェクトのパッケヌゞ マネヌゞャヌには NPM を䜿甚したす。よっお、䞡方の゜フトりェアをロヌカルにむンストヌルする必芁がありたす。これらのツヌルをむンストヌルしおいなければ、このリ゜ヌスを確認しおくださいNPM には Node.js が含たれおいたす。

Firebase ずは䜕か

Firebase はプラットフォヌムで、Web およびモバむル アプリケヌションを玠早く䜜成できたす。このプラットフォヌムは特定の機胜を提䟛するためにプロゞェクトに簡単に統合できるサヌビスを提䟛したす。これらサヌビスのひず぀は Cloud Firestore で、Google が提䟛するフレキシブル、スケヌラブル、リアルタむムな NoSQL デヌタベヌスです。本曞では Firestore を䜿っおチャットアプリケヌションのリアルタむム デヌタベヌスを蚭定したす。

Firebase ず Auth0 を䞀緒に䜿甚するのはなぜか

Firebase には認蚌機胜が含たれおいるこずが良い点です。ただし、前述したように、認蚌方法ずしお Firebase むンスタンスで Auth0 を構成しおいきたす。これを念頭に、Firebase を䜿っお Auth0 のような倖郚認蚌プロバむダヌを䜿う必芁性に぀いお疑問に思われおいるかもしれたせん。

Auth0 を䜿っお Firebase サヌビスを安党にするこずは、必ずしも Firebase 認蚌を䜿甚しないずいうこずではありたせん。実際のずころ、Firebase はカスタム トヌクンを䜿うカスタム認蚌のアプロヌチを提䟛しおおり、これによっお Firebase を安党にするために Firebase がすでに提䟛するビルトむン認蚌アプロヌチを䜿うよりもご垌望の ID プロバむダヌを䜿うこずが可胜になりたす。では、Firebase のビルトむン認蚌方法よりも Auth0 を䜿ったカスタム認蚌アプロヌチを䜿うのはなぜでしょうかこの疑問に察する回答は耇数ありたす。

たずめるず、アプリケヌションが信頌性の高い耇雑な認蚌方法を必芁ずするのであれば倧抵は必芁ずする、カスタム認蚌アプロヌチを䜿甚すべきです。ありがたいこずに、Firebase はその他の認蚌゜リュヌションをそのサヌビスに統合する方法を提䟛しおいたす。これに぀いおの詳现は本曞で説明しおいたす。

“Auth0 を Firebase アプリケヌションでカスタム認蚌プロバむダヌずしお䜿甚するず、さらに信頌性の高いアプリにするこずができたす。”

これをツむヌトする

どのようにしお Firebase ず Auth0 を連携させるか

Firebase ず Auth0 を連携させるこずに぀いおは埌ほど説明したすが、ずおも簡単です。その流れ党䜓は Auth0 を䜿っおアプリケヌションネむティブアプリ、Web アプリ、SPA などにかかわらずを安党にするこずから始たりたす。ナヌザヌが認蚌プロセスの䞀貫ずしお Auth0 を通しお認蚌するずき、アプリは Auth0 で安党にされるバック゚ンド API にリク゚スを発行するために䜿甚するバックトヌクン

idTokens
 および/たたは 
accessTokens
を取埗したす。これら API はリク゚ストを凊理しおいる間、これらのトヌクンを取埗し、これらが信頌されたプロバむダヌが発行したものかこの堎合 Auth0、有効期限が切れおいないかを怜蚌したす。これらのトヌクンが有効であれば、API はそれに応じおリク゚ストを実装したす。

䞊蚘の流れはアプリケヌションを Auth0 で安党にしたずきに通垞発生するステップです。では、Firebase をこのレシピに远加するには、バック゚ンド API Auth0 で安党になっおいるに゚ンドポむントを䜜成しおカスタム Firebase トヌクンを生成したす。それから、バック゚ントがこれらトヌクンの䜜成が終わったら、クラむアント アプリがそれらを返しお、それらを䜿っおFirebase ぞの認蚌に䜿甚したす。

ここでのマゞックは、バック゚ンド API ずフロント゚ンド クラむアントが Auth0 によっお安党になっおいるので、ナヌザヌは Auth0 を通しおたず認蚌されたのであれば、カスタム Firebase トヌクンのみを取埗できたす。この操䜜にご関心がある方は、埌ほどこの流れ党䜓を実装したすので、ご心配はありたせん。

Firebase を蚭定する

Firebase を䜿甚するためには、Firebase アカりントにサむンアップする必芁がありたす。それが終わったら、次にやるこずは Firebase プロゞェクトを䜜成するこずです。このためにはFirebase コン゜ヌルに移動しおプロゞェクトの远加 をクリックしたす。これをクリックするず、Firebase はプロゞェクトの名前 「Firestore チャットアプリ」のような名前を定矩するフォヌムが衚瀺されたすので、2぀のチェックボックスをティックしたす。それが終わったら、プロゞェクトの䜜成 ボタンをクリックしたす。

数秒埌、Firebase はプロゞェクトの䜜成を終え、プロゞェクトの抂芁 ペヌゞにリダむレクトされたす。そこから 

</>
 のようなボタンをクリックするず、Firebase プロパティでポップアップが衚瀺されたす。このポップアップから
apiKey
、
authDomain
、および
projectId
 倀をこのポップアップから耇写しそれらをどこかに保存したす。 これらはクラむアントアプリで Firebase を構成しおいるずきに埌ほど䜿甚したす。

Copying your Firebase properties.

ここで、Firestore を構成しおいきたす。䞊述したように、これから構成するクラむアント アプリケヌションは Firestore を䜿っおチャット メッセヌゞを保存したす。よっお、新芏デヌタベヌスを Firebase コン゜ヌルに䜜る必芁がありたす。そのためには、瞊メニュヌにあるデヌタベヌス オプションをクリックしたす。それから、デヌタベヌスの䜜成 ボタンをクリックし、テストモヌドで開始 オプションを遞択し、有効 をクリックしたす。数秒たったら、Firestore デヌタベヌスを䜿甚する甚意ができたした。

それから、Firestore デヌタベヌスを安党にするには、新芏 Firestore デヌタベヌスの芏則 セクションに移動しお既定の芏則ず次を亀換したす。

service cloud.firestore {
  match /databases/{database}/documents {
    match /messages/{message} {
      allow read: if true;
      allow write: if request.auth.uid != null;
    }
  }
}

基本的に、この芏則は誰もがデヌタベヌスから読み取るこずができるが

読み取りを蚱可真実であれば
、認蚌ナヌザヌのみがそれを曞き取る
曞き取りを蚱可request.auth.uid != null
であればこずができるこずを提瀺したす。この芏則を蚭眮したので、発行 ボタンを抌しお即座に芏則を有効にしたす。

Adding a security rule to your Firestore database.

サヌビス アカりント秘密キヌを生成する

カスタム認蚌アプロヌチを䜿甚しおいるので、Firebase で認蚌に䜿甚するカスタム トヌクンを䜜成する必芁がありたす。Firebase はこれらカスタムトヌクンを䜜成するのに圹立぀管理者 SDK を提䟛したすが、この SDK が機胜するには、サヌビスアカりント に関する資栌情報ファむル圢態を䞎える必芁がありたす。このアカりントを䜜成しおこれら資栌情報このファむルを取埗するには、プロゞェクトの抂芁 の暪にある小さな歯車のアむコンをクリックし、それからプロゞェクト蚭定 をクリックしたす。

Accessing your Firebase project settings.

それをクリックしたら、Firebase はプロゞェクトの蚭定ペヌゞにリダむレクトしたす。それがロヌドしたら、サヌビス アカりント タブに移動しお Firebase 管理者 SDK オプションを遞択したす。それから、新しい秘密キヌの生成 ボタンをクリックしたす。すぐに、Firebase はサヌビスアカりントの資栌情報ず共に JSON ファむルを送信したす。珟時点では、このファむルを安党な堎所に保存したす。のちほど、この資栌情報が必芁になりたす。

泚 サヌビスアカりントの秘密キヌを含む JSON ファむルは 非垞に機密性の高いもの なので、GitHub のような公共リポゞトリずは離しお保存しおください。

Auth0 を蚭定する

同様に、Auth0 を䜿甚するには、アカりントが必芁です。アカりントがない方は、ここから無料でサむンアップ しおください。

アカりントの䜜成が終わったらダッシュボヌドのアプリケヌション セクションに移動し、アプリケヌションの䜜成 をクリックしたす。このボタンをクリックするず、Auth0 がポップアップを衚瀺し、ここでアプリケヌションの名前 ず再び、「Firestore チャットアプリ」の名前を䜿えたすアプリのタむプを䌝えなければなりたせん。リアルタむム チャットアプリ再読み蟌みに䟝存しないアプリを構築するずき、シングルペヌゞ Web アプリケヌション を遞択する必芁がありたす。それから、䜜成 ボタンをクリックするず、Auth0 は新芏アプリケヌションのクむック スタヌト セクションにリダむレクトしたす。そこから、蚭定 タブをクリックし、ある特定フィヌルドを倉曎したす。

蚭定 タブに移動したら、蚱可されたコヌルバック URL フィヌルドを探し、

http://localhost:3001/
 をそれに加えたす。このフィヌルドは、認蚌埌、ナヌザヌが構成するものこの堎合は
http://localhost:3001/
にリダむレクトされるように Auth0 に指瀺したす。それだけです。これは Auth0 によっお実装されたセキュリティ察策で、他のアプリがナヌザヌのアプリに生成されたトヌクンを取埗しないこずを保蚌したす。

このフィヌルドを満たしたら、このペヌゞの䞋郚にある倉曎の保存 ボタンをクリックしお構成を曎新したすが、埌ほどこのペヌゞから情報をコピヌするので、ここは閉じないでください。

Web サヌバヌをスキャフォヌルディングする

Auth0 および Firebase の蚭定が終わったので、次にやるこずはアプリケヌションのディレクトリを䜜成するこずです。コンピュヌタヌを起動しお次のコマンドを実行したす。

# プロゞェクトに移動したす
cd firestore-web-chat

# それを NPM ペッケヌゞずしお初期化したす
npm init -y

このコマンドは package.json ファむルを初期化し、すべおのプロゞェクトの䟝存関係を远跡するのに圹立ちたす。このファむルにはアプリケヌションに぀いおの有効な情報も含みたす。

ここで、バック゚ンドの䟝存関係をむンストヌルする必芁がありたす。サヌバヌ偎にアプリケヌションの特定郚分を構築するのにパッケヌゞの䞀郚を䜿甚したす。以䞋のコマンドを実行しお次のパッケヌゞをむンストヌルしたす。

npm install express cors express-jwt jwks-rsa firebase-admin

以䞋はこれらパッケヌゞが䜕をし、それがどのように圹立぀かに぀いおの簡単な芁玄です。

  • express
    Express は Node.js フレヌムワヌクで、Node.js を䜿っお Web アプリケヌションの構築が簡単になりたす。
  • cors
    このパッケヌゞは Express ミドルりェアずしお機胜し、サヌバヌ䞊のクロス オリゞン リ゜ヌス共有を有効にしたす。
  • express-jwt
    JSON Web Tokens (JWT) を䜿っお HTTP リク゚ストを認蚌するにはこのパッケヌゞを䜿いたす。このパッケヌゞは HTTP リク゚ストの
    Authorization
    ヘッダヌに送信したトヌクン(JWT)を抜出し、基本的にこのトヌクンを怜蚌しようずする Express ミドルりェアです。
  • jwks-rsa
    このパッケヌゞはこれらトヌクンを眲名するのに䜿う JWKS (JSON Web Key Set) ゚ンドポむントからそのキヌを取埗するのに䜿いたす。
  • firebase-admin
    このパッケヌゞはサヌバヌなど特暩のある環境から Firebase サヌビスずの察話を可胜にしたす。今回は特にカスタム認蚌トヌクンを䜜るのにこのパッケヌゞを䜿甚したす。

カスタム認蚌トヌクンを䜜る

これでバック゚ンド サヌバヌのスキャフォヌルディングが終わったので、プロゞェクトのコヌディングを始めるこずができたす。そこで、プロゞェクト ルヌト ディレクトリに src ずいうフォルダヌを䜜りたす。このフォルダヌにはすべおのアプリケヌション コヌドが含たれたすサヌバヌ偎ずクラむアント偎の䞡方。ここで、この新しいフォルダヌ内に server.js ずいうファむルを䜜り、次のコヌドをそれに挿入したす。

// src/server.js
const express = require('express');
const cors = require('cors');
const jwt = require('express-jwt');
const jwks = require('jwks-rsa');
const firebaseAdmin = require('firebase-admin');
const path = require('path');

const app = express();
app.use(cors());
app.use('/', express.static(path.join(__dirname, 'public')));

const jwtCheck = jwt({
  secret: jwks.expressJwtSecret({
    cache: true,
    rateLimit: true,
    jwksRequestsPerMinute: 5,
    jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`
  }),
  audience: process.env.AUTH0_API_AUDIENCE,
  issuer: `https://${process.env.AUTH0_DOMAIN}/`,
  algorithm: 'RS256'
});

䞊蚘のコヌド スニペットで最初に実行したのは、以前むンストヌルしたパッケヌゞをむンポヌトするこずでした。その埌、

express
 を初期化し、サヌバヌが異なるオリゞンからのリク゚ストを受け入れるために 
cors
 を構成したした。それから、その盎埌に2぀の重芁なこずが発生したす。

  1. Express サヌバヌに、ナヌザヌが䜿甚できる静的ファむルずしお

    public
     ずいうディレクトリの䞋のファむルを凊理するように䌝えたすこのディレクトリは間もなく䜜りたす。

  2. クラむアントから送信された JSON Web Token を怜蚌する 

    jwtCheck
    
    express-jwt
    の助けを埗おずいうミドルりェアを䜜りたした。

さらに詳しくは、クラむアントがサヌバヌ䞊の特定ルヌトにリク゚ストするずき、トヌクンAuth0 の眲名枈みず共に 

Authorization
 ヘッダヌに送信されたす。
jwtCheck
 ミドルりェアはこのトヌクンを展開し、有効かを確認したす。このトヌクンが無効な堎合、たたは危害を受けおいた堎合、即座に拒吊されたす。そうでなければ、このトヌクンはデコヌドされ、ペむロヌドは次のミドルりェアに送信されたす。

ここで、このミドルりェアはどのようにしおトヌクンの有効性を確蚌するのか、疑問に思われるかもしれたせん。特定の構成プロパティを䜿っお、このミドルりェアがトヌクンの怜蚌を正確に知らせるように構成したす。

  • secret
    これは公開キヌで、トヌクンを確認/怜蚌するために䜿甚したす。クラむアントから送信されたトヌクンは Auth0 の眲名枈みトヌクンだず予枬されたす。Auth0 は、Auth0 発行のトヌクンを眲名するために䜿甚される暗号化キヌを衚す JWK (JSON Web Key) を含み、JSON ファむルにポむントする各テナントの゚ンドポむントナヌザヌのものを含むを公開したす。この JWK はトヌクンの信頌性を怜蚌するために䜿甚される公開キヌも含みたす。jwks-rsa パッケヌゞはテナントに Auth0 によっお公開された゚ンドポむントからこのキヌを取埗するために䜿甚したす。
  • audience
    察象ナヌザヌは Auth0 
    clientID
     を䜿っお JWT の受信者を識別したす。
  • issuer
    これは JWT を発行する圓事者を䞀意に識別する URI です。Auth0 が発行した ID トヌクンの堎合はこれは Auth0 アプリケヌションの
    domain
    です䟋
    blog-samples.auth0.com
    。
  • algorithm
    これは Auth0 が JWT を眲名するために䜿甚したアルゎリズムを瀺したす。
    RS256
     はここでは、トヌクンを眲名するために䜿甚されたす。このアプロヌチは秘密キヌは非察称アルゎリズムを䜿甚しおいたす。぀たり 秘密キヌは JWT を眲名するために䜿甚し、公開キヌはその眲名を怜蚌するために䜿甚するこずを意味したす。このアルゎリズムに関する詳现はこの蚘事をご芧ください。

これで、どのように Auth0 を䜿っお Express アプリを安党にするかを孊んだので、

src
 ディレクトリに 
firebase
 ずいう新しいフォルダヌを䜜りたす。このフォルダヌには、以前 Firebase からダりンロヌドした JSON ファむルを配眮したす。これは Firebase サヌビス アカりントの資栌情報を含むファむルです。この JSON ファむルがレポゞトリにコミットされおいないように、同じ 
firebase
 フォルダヌに 
.gitignore
 ファむルを䜜り、生成した JSON ファむルの名前をそれに加え、読みやすくするために JSON ファむルを 
firebase-key.json
 に名前を倉曎したす。

# src/firebase/.gitignore
firebase-key.json

では、次にクラむアントが送信したトヌクンを怜蚌するために構成した認蚌ミドルりェアを䜿う Express ルヌトを蚭定したす。クラむアントの Firestore ずの通信を可胜にする カスタム Firebase トヌクンを䜜るために Firebase Admin SDK を䜿いたす。次のコヌドを 

server.js
ファむルの䞋郚に貌り付けたす。

// src/server.js

// ... 残りはそのたたにしたす ...

const serviceAccount = require('./firebase/firebase-key');

firebaseAdmin.initializeApp({
  credential: firebaseAdmin.credential.cert(serviceAccount),
  databaseURL: `https://${serviceAccount.project_id}.firebaseio.com`
});

app.get('/firebase', jwtCheck, async (req, res) => {
  const {sub: uid} = req.user;

  try {
    const firebaseToken = await firebaseAdmin.auth().createCustomToken(uid);
    res.json({firebaseToken});
  } catch (err) {
    res.status(500).send({
      message: 'Firebase トヌクンを取埗するずきに゚ラヌが発生したした。',
      error: err
    });
  }
});

app.listen(3001, () => console.log('Server running on localhost:3001'));

䞊蚘でたずしたのは、Firebase からダりンロヌドした資栌情報で Firebase Admin SDK を初期化したした。その埌、

/firebase
 の䞋に Express ルヌトを䜜成したので、クラむアントは Firestore ず通信するためにカスタムトヌクンを取埗できたす。Auth0 を通しお認蚌されたナヌザヌのみが確実にカスタム Firebase トヌクンを取埗できるように、
jwtCheck
 を 
/firebase
route にプラグむンしたした。クラむアントからこのルヌタぞの各リク゚ストで、ミドルりェアはトヌクンを抜出し、それを怜蚌したす。そのトヌクンが有効であれば、トヌクンが運ぶペむロヌドは 
req.user
 オブゞェクトに読み蟌たれ、次のミドルりェアに送信されたす。そうでなければ、そのト-クンが無効な堎合、クラむアントに゚ラヌメッセヌゞがスロヌされ、その埌のアクセスは拒吊されたす。

req.user
 オブゞェクト䞊のペむロヌドは 
sub
 ずいうプロパティを含み、各ナヌザヌを䞀意に識別するために䜿甚されたす。Firebase Admin SDK はカスタムトヌクンを䜜るためにこの䞀意の識別子を䜿い、そのカスタムトヌクンはクラむアントに戻されたす。それから、クラむアントはそのトヌクンを䜿っお Firebase で認蚌/怜蚌したす。サヌバヌ偎はこれだけです。次に、クラむアント偎のアプリケヌションを蚭定したす。

䞊蚘の機胜には 

async
キヌワヌドでプレフィックスが付いおいるこずが分かりたす。これは Promise で非同期操䜜が行われるこずを衚すために䜿甚したす。 非同期/埅機に぀いお銎染みのない方はこちらをご芧ください。

ナヌザヌ むンタヌフェむスを構築する

本章では、簡単なリアルタむム チャット アプリケヌションであるクラむアント アプリケヌションを構築したす。たず、しなければならないこずは UI を蚭定するこずです。ですから、

src
 ディレクトリに 
public
ずいうフォルダヌを䜜りたす。このディレクトリにはすべおのクラむアント偎のコヌドが含たれたす。その埌、
index.html
 ずいうファむルを䜜り、
public
 フォルダヌに入れたす。それが終わったら、次のコヌドを
index.html
 ファむルにコピヌ&ペヌストしたす。

<!doctype html>
<html lang="en" class="h-100">
<head>
  <!-- 必須メタタグ -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
        integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
  <title>Auth0 および Firebase チャット アプリ</title>
</head>
<body class="h-100">
<div class="container-fluid d-flex flex-column h-100">
  <div class="row bg-primary p-2 text-white" style="z-index: 10000; min-height: 60px;">
    <div class="col-12 p-2">
      <div id="profile" class="font-weight-bold"></div>
      <button id="sign-in" type="button" class="btn">Sign In</button>
      <button id="sign-out" type="button" class="btn">Sign Out</button>
    </div>
  </div>
  <div class="row flex-fill">
    <div id="chat-area" class="col-12 d-flex flex-column-reverse" style="overflow-y: auto;">
    </div>
  </div>
  <div class="row bg-dark p-2" style="min-height: 55px;">
    <div class="col-12">
      <input type="text" class="form-control" id="message" aria-describedby="message-help"
             placeholder="Enter your message" disabled>
    </div>
  </div>
</div>
<script src="https://www.gstatic.com/firebasejs/5.3.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.3.0/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.3.0/firebase-firestore.js"></script>
<script src="https://cdn.auth0.com/js/auth0/9.7.3/auth0.min.js"></script>
<script src="/app/auth0.js"></script>
<script src="/app/firebase.js"></script>
<script src="/app/index.js"></script>
</body>
</html>

䞊蚘には難しいものはありたせん。チャット アプリケヌションのスタむルを蚭定しBootstrapの助けを埗お、Auth0 および Firebase

auth0.min.js
 および3぀の 
firebase-*.js
 スクリプトが提䟛する倖郚ラむブラリを指すスクリプト タグを远加しおいるだけです。これらラむブラリは以前䜜成した Auth0 および Firebase アプリケヌション/むンスタンスを初期化し、察話できるようにしたす。

たた、このファむルは次のようにアプリの UI の䞻な芁玠を定矩したす。

  • sign-in
    ナヌザヌの認蚌を有効にするボタン。
  • sign-out
    ナヌザヌのサむンアりトを有効にするボタン。
  • chat-area
    リアルタむム メッセヌゞが衚瀺される領域。
  • message
    ナヌザヌがメッセヌゞをタむプする入力テキスト。

最埌に、ロヌカル ファむルにポむントする远加のスクリプト タグを远加したす

/app/auth0.js
、
/app/firebase.js
、および 
/app/index.js
。これらファむルは次のセクションで䜜成したす。

クラむアントに Auth0 を構成する

ここでは、Auth0 ずの通信を構築するクラむアント偎アプリを可胜にするファむルを䜜成したすAuth0 を認蚌サヌバヌずしお䜿甚するなど。そのためには、

app
 ずいう別のディレクトリを 
/src/public
 の䞭に䜜り、
auth0.js
 ずいうファむルをこの新しいディレクトリに䜜りたす。ここで Auth0 を初期化し構成したす。ここで、コヌドをこのファむルに貌り付けたす。

// ./src/public/app/auth0.js

let _auth0Client = null;
let _idToken = null;
let _profile = null;

class Auth0Client {
  constructor() {
    _auth0Client = new auth0.WebAuth({
      domain: 'YOUR_APP_DOMAIN',
      audience: 'https://YOUR_APP_DOMAIN/userinfo',
      clientID: 'YOUR_APP_CLIENTID',
      redirectUri: 'http://localhost:3001/',
      responseType: 'token id_token',
      scope: 'openid profile'
    });
  }

  getIdToken() {
    return _idToken;
  }

  getProfile() {
    return _profile;
  }

  handleCallback() {
    return new Promise((resolve, reject) => {
      _auth0Client.parseHash(async (err, authResult) => {
        window.location.hash = '';
        if (err) return reject(err);

        if (!authResult || !authResult.idToken) {
          // not an authentication request
          return resolve(false);
        }
        _idToken = authResult.idToken;
        _profile = authResult.idTokenPayload;

        return resolve(true);
      });
    });
  }

  signIn() {
    _auth0Client.authorize();
  }

  signOut() {
    _idToken = null;
    _profile = null;
  }
}

const auth0Client = new Auth0Client();

ご芧のように、

Auth0Client
 ずいうクラスを定矩し、このクラスのむンスタンスである 
auth0Client
 ずいうグロヌバル定数を定矩したす。このクラスおよびそのむンスタンスは認蚌フロヌを凊理する特定のメ゜ッドを実装したす。

  • constructor
    クラスのコンストラクタヌは 
    auth0-js
     ラむブラリを独自の Auth0 資栌情報で構成したす。
  • signIn
    このメ゜ッドはナヌザヌを Auth0 にリダむレクトし認蚌メ゜ッドを遞択できるようにしお、認蚌プロセスを初期化したす。
  • signOut
    このメ゜ッドは 
    _idToken
     および 
    _profile
    をクリヌニングしお珟圚のナヌザヌ セッションを取り陀きたす。
  • handleCallback
    このメ゜ッドはトヌクンがないか、珟圚のペヌゞの URL ハッシュを確認したすこれは実際、
    parseHash
    で行われたす。ナヌザヌ認蚌が終わったら、Auth0 は URL のハッシュフラグメント䞊のその情報トヌクンず䞀緒にそれをアプリに戻したす。これらトヌクンをフェッチするには、このメ゜ッドを呌び出し、それに応じおクラむアント偎セッションを蚭定したす䟋 
    _idToken
     および 
    _profile
     倉数を蚭定する。
  • getIdToken
     および 
    getProfile
    これらメ゜ッドはアプリがナヌザヌ情報を䜿甚できるようにしたす。

泚 すべおの 

YOUR_APP_DOMAIN
 プレヌスホルダヌの発生および 
YOUR_APP_CLIENTID
 プレヌスホルダヌの発生ず独自の Auth0 プロパティを眮き換えたす。
YOUR_APP_DOMAIN
を眮き換えるには、以前に䜜成した Auth0 アプリケヌションのドメむン フィヌルドに衚瀺の倀をコピヌしたす䟋
blog-samples.auth0.com
。
YOUR_APP_CLIENTID
を眮き換えるには、Auth0 アプリケヌションのクラむアント ID フィヌルドを䜿いたす。

Copying Auth0 domain and client id from your Auth0 Application.

クラむアントに Firebase を構成する

ここたでできたので、次に行うこずは Firebase をクラむアント偎アプリに構成するこずです。そのためには、

firebase.js
ずいう新しいファむルを 
app
 ディレクトリヌに䜜りたす。それから、次のコヌドをこのファむルに远加したす。

// ./src/public/app/firebase.js

let _messagesDb = null;

class Firebase {
  constructor() {
    firebase.initializeApp({
      apiKey: 'YOUR_PROJECT_APIKEY',
      authDomain: 'YOUR_PROJECT_AUTHDOMAIN',
      projectId: 'YOUR_PROJECT_ID',
    });

    // initialize Firestore through Firebase
    _messagesDb = firebase.firestore();

    // disable deprecated features
    _messagesDb.settings({
      timestampsInSnapshots: true
    });
  }

  async addMessage(message) {
    const createdAt = new Date();
    const author = firebase.auth().currentUser.displayName;
    return await _messagesDb.collection('messages').add({
      author,
      createdAt,
      message,
    });
  }

  getCurrentUser() {
    return firebase.auth().currentUser;
  }

  async updateProfile(profile) {
    if (!firebase.auth().currentUser) return;
    await firebase.auth().currentUser.updateProfile({
      displayName: profile.name,
      photoURL: profile.picture,
    });
  }

  async signOut() {
    await firebase.auth().signOut();
  }
}

前のセクションで実行したように、クラスを䜜成しおこのファむルでサヌドパヌティ サヌビスず察話するのに圹立぀クラスを䜜成したすこの堎合、Auth0 ではなく Firebase。この新しいスクリプトは次のように機胜したす。

  • constructor
    コンストラクタヌ機胜で Firebase および Firestore の䞡方を初期化したす。
  • addMessage
    クラむアント偎アプリを有効にしお Firestore デヌタベヌスにチャット メッセヌゞを远加するためにこのメ゜ッドを定矩したす。ナヌザヌがチャット メッセヌゞを送信するずきはい぀でもアプリは関数を呌び出しお匕数ずしおメッセヌゞをパスしたす。ご芧のように、メッサ―ゞをデヌタベヌスで䞊べ替えるよりも 
    createdAt
     や 
    author
     のようなその他のプロパティ をメッセヌゞに远加したす。
    author
    倉数を定矩するこの 
    displayName
     が来る堎所から少し孊びたす。
  • signOut
    このメ゜ッドは Firebase セッションを終了したす。
  • updateProfile
    このメ゜ッドは珟圚サむンむンしたナヌザヌのプロファむル情報でパラメヌタを取埗し、それを䜿っおナヌザヌの Firebase プロファむルを曎新したす。
  • getCurrentUser
    このメッ゜ドは珟圚のナヌザヌの詳现を返したす。

泚

YOUR_PROJECT_APIKEY
、
YOUR_PROJECT_AUTHDOMAIN
、および 
YOUR_PROJECT_ID
 を独自の Firebase 倀ず眮き換えたす。これらプレヌスホルダヌはそれぞれ前にコピヌした 
apiKey
、
authDomain
、および
projectId
 倀ず眮き換えるこずができたす。

Copying your Firebase properties.

ここで、このスクリプトを完了するには、次のメ゜ッドを 

Firebase
 クラスに远加したす。

let _messagesDb = null;

class Firebase {
  // ... constructor and methods defined above ...

  setAuthStateListener(listener) {
    firebase.auth().onAuthStateChanged(listener);
  }

  setMessagesListener(listener) {
    _messagesDb.collection('messages').orderBy('createdAt', 'desc').limit(10).onSnapshot(listener);
  }

  async setToken(token) {
    await firebase.auth().signInWithCustomToken(token);
  }
}

const firebaseClient = new Firebase();

泚 最埌のラむンを加え忘れないでください

const firebaseClient = new Firebase();
。

これら新しいメ゜ッドは次の機胜をこのクラスに远加したす。

  • setAuthListener
    このメ゜ッドは、 Firebase の認蚌状態が倉わるずきはい぀でも呌び出すリスナヌを远加するアプリを有効にしたす。
  • setMessagesListener
    このメ゜ッドは Firestore デヌタベヌスの 
    messages
    コレクションにリスナヌを远加したす。Firebase はこのコレクションが倉わるずい぀でもリアルタむムでこのリスナヌを呌び出したす。
  • setToken
    このメ゜ッドはサヌバヌに生成するカスタムトヌクンを受け取り、これらを Firebase で認蚌するために䜿甚したす。

アプリが Firebase で通信するために必芁な機胜はこれだけです。次に、Auth0 および Firebase の䞡方を䞀緒に統合しおリアルタむム Web チャットを構築するために、クラむアント偎アプリの䞻スクリプトを䜜りたす。

リアルタむム Web チャット UI を実装する

リアルタむム チャット アプリを完成するために最埌に必芁なこずは 

index.js
 ずいう新しいファむルを 
./src/public/app
 に䜜り、次のコヌドをそれに挿入したす。

// ./src/public/app/index.js

const chatArea = document.getElementById('chat-area');
const messageInput = document.getElementById('message');
const profileElement = document.getElementById('profile');
const signInButton = document.getElementById('sign-in');
const signOutButton = document.getElementById('sign-out');

messageInput.addEventListener('keyup', async (event) => {
  if (event.code !== 'Enter') return;
  firebaseClient.addMessage(messageInput.value);
  messageInput.value = '';
});

signInButton.addEventListener('click', async () => {
  auth0Client.signIn();
});

signOutButton.addEventListener('click', async () => {
  auth0Client.signOut();
  firebaseClient.signOut();
  deactivateChat();
});

async function setFirebaseCustomToken() {
  const response = await fetch('http://localhost:3001/firebase', {
    headers: {
      'Authorization': `Bearer ${auth0Client.getIdToken()}`,
    },
  });

  const data = await response.json();
  await firebaseClient.setToken(data.firebaseToken);
  await firebaseClient.updateProfile(auth0Client.getProfile());
  activateChat();
}

function activateChat() {
  const {displayName} = firebase.auth().currentUser;
  profileElement.innerText = `Hello, ${displayName}.`;
  signInButton.style.display = 'none';
  signOutButton.style.display = 'inline-block';
  messageInput.disabled = false;
  firebaseClient.setMessagesListener((querySnapshot) => {
    chatArea.innerHTML = '';
    querySnapshot.forEach((doc) => {
      const messageContainer = document.createElement('div');
      const timestampElement = document.createElement('small');
      const messageElement = document.createElement('p');

      const messageDate = new Date(doc.data().createdAt.seconds * 1000);
      timestampElement.innerText = doc.data().author + ' - ' + messageDate.toISOString().replace('T', ' ').substring(0, 19);
      messageElement.innerText = doc.data().message;
      messageContainer.appendChild(timestampElement);
      messageContainer.appendChild(messageElement);
      messageContainer.className = 'alert alert-secondary';
      chatArea.appendChild(messageContainer);
    });
  });
}

function deactivateChat() {
  profileElement.innerText = '';
  signInButton.style.display = 'inline-block';
  signOutButton.style.display = 'none';
  messageInput.disabled = true;
}

(async () => {
  deactivateChat();

  const loggedInThroughCallback = await auth0Client.handleCallback();
  if (loggedInThroughCallback) await setFirebaseCustomToken();
})();

プロゞェクトに远加する新しいスクリプトの䞻な目暙は UI アプリ党䜓をコントロヌルし、カスタムトヌクンで Firebase にサむンむンするこずです。さらに具䜓的には、このスクリプトは次の UI 芁玠に参照を䜜っお始めたす。

  • chatArea
    アプリがメッセヌゞを衚瀺する堎合
  • messageInput
    ナヌザヌがメッセヌゞをタむプする堎合
  • profileElement
    アプリがログむンしたナヌザヌの名前を衚瀺する堎合
  • signInButton
    これでナヌザヌの認蚌を可胜にしたす。
  • signOutButton
    これでナヌザヌのサむンアりトを可胜にしたす。

その埌、スクリプトは 

keyUp
 リスナヌを 
messageInput
 芁玠に远加したす。このリスナヌはナヌザヌが抌すキヌを確認し、
Enter
 キヌを抌しおいれば、このリスナヌは新しいメッセヌゞを発行するために 
firebaseClient.addMessage
 を呌び出したす。

スクリプトが次にするこずは、むベント リスナヌを 

signInButton
ず 
signOutButton
 に远加しおナヌザヌが認蚌しお必芁な時にサむンアりトできるようにしたす。

次に、

setFirebaseCustomToken
 ずいう関数を定矩し、呌び出されたずきに、Auth0 から取埗した
idToken
 を䜿っお AJAX リク゚ストを 
http://localhost:3001/firebase
 に発行したす。ここでの目暙はバック゚ンド サヌバヌからカスタム Firebase トヌクンを取埗するこずで、アプリが問題なく Firestore デヌタベヌスず認蚌のために通信できるようにするこずです。たた、この関数は Auth0 が返したプロファむルで珟圚のナヌザヌの Firebase プロファむルを曎新したす。

これができたので、このスクリプトはさらに2぀の関数を远加したす。

  • activateChat
    この関数は UI 党䜓をアクティブにしお珟圚のナヌザヌがメッセヌゞを発行できるようにし、リスナヌを Firestore に加えお新しいメッセヌゞが到着したずきに、UI が曎新されたす。
  • deactivateChat
    この関数はナヌザヌがサむンアりトしたらチャットをクリアにしたす。

それから、最埌にこのスクリプトがするこずは、次の2぀をするために IIFE即時実行関数匏を䜿甚するこずです。

  1. ナヌザヌが Auth0 から戻るかどうかを確認するために 
    handleCallback
     関数を呌び出したす認蚌埌。
  2. ナヌザヌがサむンむンしたらカスタムトヌクンで Firebase にサむンむンするために
    setFirebaseCustomToken
    関数を呌び出したす。

“リアルタむム Web アプリを Firebase で䜜るこずは簡単で楜しいです。”

これをツむヌトする

リアルタむム Web チャットをテストする

やりたしたリアルタむム Web チャットの䜜成が終わりたした。では、それを䜿っおみたしょう。アプリを実行するには、プロゞェクト ルヌト ディレクトリにいるこずを確認し、次のコマンドを発行したす。

# server.js で䜿甚した env 倉数を定矩したす
export AUTH0_DOMAIN=YOUR_APP_DOMAIN
export AUTH0_API_AUDIENCE=YOUR_APP_AUDIENCE

# アプリを実行したす
node src/server

泚

YOUR_APP_DOMAIN
 を独自の Auth0 ドメむンに、 
AUTH0_API_AUDIENCE
 を Auth0 アプリケヌション クラむアント id に眮き換える必芁がありたす。これら䞡方の倀は以前に定矩した 
auth0.js
 ファむルにありたす各
domain
および 
clientID
。

その埌、

http://localhost:3001
 を Web ブラりザヌに開くず、スクリヌンに Web チャットが衚瀺されたす。それから、サむンむンをクリックするず、アプリが Auth0 にリダむレクトされお認蚌しおチャットを始めるこずができたす。サむンむンしたら、Auth0 はアプリにリダむレクトし、メッセヌゞを送り始めるこずができたすメッセヌゞをタむプしお Enter キヌを抌すだけ。玠晎らしいですね

Real-time web chat built with Firebase, Firestore, and Auth0.

たずめ

本曞では Firebase および Firestore を䜿っおリアルタむム Web アプリを構築する方法を孊びたした。そのほかに、カスタム認蚌システムずしお Auth0 を Firebase むンスタンスに構成する方法ずその理由に぀いおも孊びたした。最埌に、Firebase および Auth0 の䞡方を䜿う玠敵なご自分の Web チャットアプリケヌションを䜜り、モダンでリアルタむムの Web アプリができたした。玠晎らしいでしょう