前回、ポムさんがFirebaseのチャット作成の記事を書きましたが、これそのままでは投稿者のユーザ判定は行われておらず、サービスに組み込むことはできません。
そこで今回はFirebaseのユーザ認証のうち、既存のサービスとの連携が可能なカスタム認証のやり方について書きたいと思います。
※この記事におけるFirebaseのバージョンは3.1.0です。
カスタム認証では他の認証方式とは異なり、Firebase内にユーザの認証情報を持ちません。
そのため、下記の手順で認証を行うことになります。
既存サービスのサーバでユーザ認証を行う
↓
サーバから既存のユーザIDでFirebaseにアクセストークンを発行してもらう
↓
アクセストークンをクライアントに返す
↓
クライアントがアクセストークンでFirebaseにログイン
↓
既存のユーザIDでFirebaseにアクセス可能
■秘密鍵の生成
既存サービスのサーバとFirebase間のアクセスは、秘密鍵でセキュリティを担保します。
そのためまず始めに、サーバに置く秘密鍵を生成する必要があります。
※Firebaseのプロジェクトが既に作成されている前提とします。
下記のURLからGoogle Cloudの証明書ページに飛びます。
https://console.developers.google.com/projectselector/apis/credentials
認証したいFirebaseのプロジェクトを選択してください。
認証情報 → 認証情報を作成 → サービスアカウントキー の順で選択してください。
「新しいサービスアカウント」で、サービスアカウント名には適当な値を入れ、キーのタイプはJSONとして作成してください。
キーがダウンロードされます。
ポップアップの通り、このキーは再発行されないため、取り扱いには気を付けてください。
■サーバ側設定
今回はPHPで実装を行います。
下記のページを参考にしています。PHP以外については下記ページを参照してください。
https://firebase.google.com/docs/auth/server/create-custom-tokens
・ライブラリの導入
composerを使用している場合、下記のコマンドを実行してください
1 |
#php composer.phar require firebase/php-jwt |
composerを使用しない場合、下記からダウンロードして直接入れてください。
https://github.com/firebase/php-jwt
・アクセストークン発行プログラム
サンプルプログラムは下記の通りです。
IDとパスワードをPOSTするとアクセストークンを返すプログラムになります。
実際の実装時にはセッションからユーザIDを取得する等の処理になるかと思います。
・test-auth.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?php function login($id, $pass){ // ①適宜サービスのログイン機構に差し替えて下さい $id = $id; $permit = $pass; return [$id, $permit]; } require '../vendor/autoload.php'; // ②適宜読み替えてください use Firebase\JWT\JWT; $service_account_email = "test-custom-auth@xxxxx.iam.gserviceaccount.com"; // ③秘密鍵JSONファイルの"client_email"の値を設定してください $private_key = "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"; // ④秘密鍵JSONファイルの"private_key"の値を設定してください function create_custom_token($uid, $option) { global $service_account_email, $private_key; $now_seconds = time(); $payload = array( "iss" => $service_account_email, "sub" => $service_account_email, "aud" => "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit", "iat" => $now_seconds, "exp" => $now_seconds+(60*60), // Maximum expiration time is one hour "uid" => $uid, "claims" => array( "custom_permit_option" => $option // ⑤ここで設定した値は、パーミッションのルール内で"auth.token"のメンバとして参照できるようです。 ) ); return JWT::encode($payload, $private_key, "RS256"); } list($user_id, $permit) = login($_POST["id"], $_POST["pass"]); if($user_id != null) echo create_custom_token($user_id, $permit); |
■パーミッションルール設定
FirebaseコンソールのDatabase → ルールからパーミッションのルールを設定します。
ルールの記法等についてはここでは割愛しますが、テストプログラム動作確認のため、下記のように設定します。
1 2 3 4 5 6 7 8 9 |
{ "rules": { ".read":false, ".write":false, "test1":{".read": "auth != null"}, "test2":{".read": "auth.uid == 'admin'"}, "test3":{".read": "auth.token.custom_permit_option == 'test'"} } } |
また、Database → データから”test1″,”test2″,”test3″を設定しておきましょう。
1 2 3 4 5 |
{ "test1":"aaa", "test2":"bbb", "test3":"ccc" } |
■クライアント
https://localhostで下記のページを動作させます。
localhost以外で動かしたい場合には、コンソールからAuth → ログイン方法 → OAuth リダイレクト ドメイン にドメインを追加してください。
※httpsでないとFirebaseのアクセス時にエラーが出ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Custom Auth Sample</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script> <script src="https://www.gstatic.com/firebasejs/3.1.0/firebase.js"></script> <script src="auth-test.js"></script> </head> <body> <div class='login'> <h2>Login</h2> <form name="login"> E-mail:<input type="text" name="user_id" /><br/> Password:<input type="password" name="password" /><br/> <input type="submit" value="send" /> </form> </div> <hr/> <h2>datas</h2> <table id="datas"> <tr><th>test1</th><td id="data1"></td></tr> <tr><th>test2</th><td id="data2"></td></tr> <tr><th>test3</th><td id="data3"></td></tr> </table> </body> </html> |
・auth-test.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
var config = { // 適宜読み替えてください apiKey: "XXX", authDomain: "xxx.firebaseapp.com", databaseURL: "https://xxx.firebaseio.com", storageBucket: "xxx.appspot.com", }; var uid = null; var app = firebase.initializeApp(config); var ref = app.database().ref(); window.onload = function(){ document.forms.login.onsubmit = login; }; function login(event){ var user_id = this.user_id.value; var password = this.password.value; this.disabled = true; $.ajax({ type:'POST', url:'https://localhost/test-auth.php', data:{id:user_id, pass:password}, dataType: 'text' }).done(function(res, status, jqXHR) { app.auth().signInWithCustomToken(res.replace('\n')).then( function(e){ console.log('success'); uid = app.auth().currentUser.uid; this.disabled = false; this.startSync(); },function(e){ console.log('failer'); console.log(e); this.disabled = false; }); }).fail(function(jqXHR, status, error ) { console.log('failer :' + status); console.log(error); this.disabled = false; }); return false; } function startSync(){ ref.child('test1').once('value', function(r){$('#data1').text(r.val())}, function(r){$('#data1').text("invalid")}); ref.child('test2').once('value', function(r){$('#data2').text(r.val())}, function(r){$('#data2').text("invalid")}); ref.child('test3').once('value', function(r){$('#data3').text(r.val())}, function(r){$('#data3').text("invalid")}); } |
■実行結果
ログイン後、”test1″,”test2″,”test3″の右にルールに設定した条件に当てはまる場合には設定値が、当てはまらない場合には”invalid”が表示されるはずです。
上記のソースコードそのままであれば、”test1″は必ず、”test2″はE-mailが”admin”のとき、”test3″はPasswordが”test”のときに表示されるでしょう。
■まとめ
claimsの値を使えば、複雑なアクセス制御もわりと簡単に実装できそうです。
秘密鍵が漏れるとアクセスし放題なので、当然ながらセキュリティには気を付けること。
実際に利用する際には既存のユーザIDそのものではなく、Firebase上で利用するためのユーザ毎に一意なIDを別に作成して利用するほうがセキュアでしょう。