認証について

※認証をすることがなぜ重要かを知りたい方は、「認証の必要性」の項に移動下さい。

認証とは、あなたのMilkcocoaアプリに対して、人やIoTデバイス(以下、まとめて「ユーザー」と呼びます)が「本人しか知り得ない情報使って、本人であることを証明すること」です。認証されたユーザーはIDを持っていて、MilkcocoaアプリからそのIDによって識別されます。

認証はMilkcocoaの認証用のAPI(命令)を使ってすることになりますが、ただ単にAPIを発行すれば認証できるということはもちろんありません。それができると、あらゆるサイトからあなたのアプリに認証が出来るようになってしまいます。

その認証API発行が、ちゃんと「アプリの管理者(あなた)が管理しているサイトの利用者やIoTデバイスからのもの」だと証明できなければいけません。つまり、管理者しか知らない情報を含ませたかたちで認証API発行することで、認証が成功するようになっています。

では、具体的な認証方法を見てみましょう。認証API発行に管理者しか知らない情報を含ませる方法は、以下の2種類になります。

  • API Key認証
  • トークン認証

「API Key認証」はシンプルで、アプリの管理画面で発行できる(管理者しか知り得ない)「API Key」と「API Secret」のペアを認証API発行に使う方法です。API KeyやAPI Secretが外部から見られないような環境でのみ利用できます。

API KeyはAPIを使うためのKeyであると同時に、アクセス制御の単位(ID)でもあります。IDと言っていますが、使うデータストアやアクセス制御の条件が変わらないのであれば、同じAPI Keyを複数のIoTデバイスに使い回すことができます(あるWebサービスに対して同じアカウントでPCやスマホなど複数のデバイスからログインする感じ、というと想像しやすいかと思います)。

IoTデバイスで利用する場合などはAPI Key認証で十分なのですが、Webの場合、ソースコードをWeb上に公開するためその認証方法は使えません。そうしてしまうと、ソースコードを見られた時点でAPI KeyとAPI Secretが知られてしまいます。

そこで、Webでは「トークン認証」を使います。

「トークン認証」では、管理者しか知り得ない情報を隠蔽するために、認証用のサーバー(以後、認証サーバー)を使います。具体的には、管理者しか知り得ない「Secret Key」を認証サーバーに教えます。

そして認証API発行の際には、まずその認証サーバーにアカウント情報を伝えます。アカウント情報を伝えると、認証サーバーがそのアカウント情報を、Milkcocoaアプリから教えてもらった「Secret Key」を使ってトークンに変換して返してくれます。そのトークンをつかってMilkcocoaアプリに認証API発行をします。

この場合、ユーザーが認証サーバーに投げた「アカウント情報」の中のいずれかがIDにあたります(詳しい話は具体的な実装方法のセクションで説明します)。

また、あなたのWebサイトがPHP等のサーバーサイド言語を使用していれば、そのコードにSercret Keyを隠蔽することができます(その場合、サーバーサイドにユーザー情報を管理するためのアカウントシステム(アカウントの新規登録・ログイン)が必要です)。

まとめると、IoTデバイスなど(あなたの管理するユーザーのみ)の認証では「API Key認証」、Web(不特定多数のユーザー)の認証では「トークン認証」を使うことになるかと思います。

それでは、具体的な実装方法について説明します。

API Key認証の実装方法

先ほど説明したように、「API Key」と「API Secret」を使って認証する方法です。

実装方法は、Milkcocoaとコネクションを確立するときにAPI KeyとAPI Secretを送信するだけで認証できます。具体的にはmilkcocoaオブジェクトを作成するコードを以下のように変更します。

JavaScript(認証無し)
var milkcocoa = new MilkCocoa('your-app-id.mlkcca.com');
JavaScript(認証有り)
var milkcocoa = MilkCocoa.connectWithApiKey('app-id.mlkcca.com', 'API_Key', 'API_Secret');
Arduino(認証無し)
Milkcocoa milkcocoa = Milkcocoa(&client, MQTT_SERVER, MILKCOCOA_SERVERPORT, MILKCOCOA_APP_ID, MQTT_CLIENTID);
Arduino(認証有り)
Milkcocoa *milkcocoa = Milkcocoa::createWithApiKey(&client, MQTT_SERVER, MILKCOCOA_SERVERPORT, MILKCOCOA_APP_ID, MQTT_CLIENTID, "API_Key", "API_Secret");

API KeyとAPI Secretは、アプリの管理画面の「認証」 > 「API Key認証」で生成・確認することができます。

トークン認証の実装方法

アカウント情報をSecret Keyを使って変換されたトークンを使って認証をする方法です。先ほど説明したように、「認証サーバーを用意する方法」と「サーバーサイドのコードにSecret Keyを書く方法」があります。

どちらの方法も、Secret Keyを使って最終的にトークンに変換されます。そのトークンを使って認証する方法(認証API)は以下になります。

// var milkcocoa = new MilkCocoa('your-app-id.mlkcca.com');
milkcocoa.authWithToken(token, function(err, user) { /* 認証処理が終わったら呼ばれる関数 */ });

Secret Keyはアプリの管理画面の「認証」 > 「トークン認証」で確認・再発行することができます。

また、このトークンの形式はJSON Web Tokenの仕様に則っています。JSON Web Tokenでは、トークンのどのプロパティに何の情報を入れるかが決まっていて、例えば、ユーザーのIDはsubというプロパティに設定します。詳しい仕様については、JSON Web Token公式サイトの説明をご覧下さい。

認証サーバーを用意する方法

認証サーバーを自前で用意するのはなかなか難易度が高いかと思います。そもそもバックエンドを簡単化するMilkcocoaを使っておきながら、認証サーバーは自前で用意する、というのはおかしな話です。

そこで、認証サーバーをサービス化したAuth0というサービスを使うことをお勧めしています。

Auth0を使ったトークン認証方法については、別途ブログ記事に書いたので、そちらを参照頂ければと思います。

サーバーサイドのコードにSecret Keyを書く方法

あなたのWebサイトで既に、アカウントシステムが実装されていればこちらの方法がオススメです。そのアカウント情報をMilkcocoaに流用できるからです。

あなたのWebサイトのアカウント情報を、JSON Web Tokenのライブラリ(JWT公式サイトで言語ごとのライブラリがまとめてあります)を使ってトークンに変換します。変換の際にはSecret Keyを使います。

具体的には、以下のようなコードになります(一例です)。

<?php
// JWTライブラリの読み込み
require_once(dirname(__FILE__) . "/jwt/JWT.php");
use Firebase\JWT\JWT as JWT;

$current_user; // 現在のログインユーザー情報が格納されているとします
$url = (empty($_SERVER["HTTPS"]) ? "http://" : "https://") . $_SERVER["HTTP_HOST"];

// こちらはトークン化する前のJSONです(仮にトークンの有効期間を30000秒にした場合)。JWTの仕様上、発行元URL・トークンの生成時刻・有効期限も入力します。
$json = json_decode('{"iss":"'.$url.'","sub":"'.$current_user->user_id.'","iat":'.time().',"exp":'.(time()+30000).'}');

// Secret Keyを使ってトークン化します
$jwt = JWT::encode($json, 'Secret_Key');
// この値をJavaScriptに渡して、Milkcocoaの認証APIで使用します。
?>

JSONを作成する部分を見てみると、subuser_idを渡しているのがわかるかと思います。このようにユーザーのIDにあたる部分は、subに入れるようお願いします。

認証の必要性

認証の必要性の前に「認証済みユーザー」について説明します。認証済みユーザーは、以下のような性質があります。

  • 管理者の管理している範囲で認証を行っているので、信頼性がある
  • それぞれのユーザーをIDによって識別できる

認証をしていないユーザーは、どこから接続されているかも不明である上、ユーザーの情報が把握できないためアプリ側から特定できません。

さて、認証済みユーザーがこういう性質を持っていることを踏まえた上で、アプリを想定された用途で安全に利用するために以下の機能を用意しています。

  • 認証していないユーザーの接続を不可にする
  • ユーザーのIDを使って、データストアのアクセス制御を行う

それぞれについて具体的な実装方法を紹介します。

認証していないユーザーの接続を不可にする

認証していないユーザーは、「想定したアプリから利用していない」ことになるため、認証していないユーザーの接続を不可にすることで、想定外の不正な利用を遮断できます。

利用方法は、アプリの管理画面の「認証」 > 「認証済みクライアント以外を接続不可にする」をONにするだけです(デフォルトでONになっています)。

ユーザーのIDを使って、データストアのアクセス制御を行う

セキュリティルールを使って、「ユーザーごとに権限を持ったデータストアを作成する」といったことが可能です。

メモアプリを例にとってみましょう。「ユーザーごとにメモを所有していて、投稿は自分しかできないが、閲覧は誰でも出来る」というシステムにしてみます。メモごとに、ハッシュ(#)以下にオーナーのユーザーIDが入ったURLを持っていることにします(オーナーのユーザーIDがhogeのメモはhttp://example.com#hoge)。

まず以下のようなセキュリティルールを書きます(セキュリティルールの書き方については、セキュリティルールのドキュメントをご覧下さい)。

memo/* {
  permit : query;
  rule : true;
}
memo/[userID] {
  permit : push;
  rule : account.sub == userID;
}

memo以下は、データの取得は誰でも出来て、データの保存は自分のユーザーIDと同じ名前のデータストアにしか出来ない、といった感じです。

そして、JavaScriptのコードは以下のようになります(オーナーがいないメモにアクセスしたときの処理等は割愛しています)。

// authWithTokenで認証は済んでいるとする

var ownerID = '';
if(location.hash) ownerID = location.hash.slice(1);

var ds = milkcocoa.dataStore('memo').child(ownerID);

// メモの取得(誰でもできる)
ds.stream().next(function (err, data) {
  // dataにメモのデータ
});

submitButton.addEventListener('click', function (e) {
  // メモの投稿(オーナーしか出来ない)
  ds.push({...});
});

// 現在の認証済みユーザーの情報を取り出してみる
milkcocoa.user(function(err, user) {
    if (err) { console.error(err); return; }
    if (user) {
      if(user.sub === ownerID) console.log('You are owner!');
      else console.log('You are not owner...');
    }
});

これだけで、「ユーザーごとにメモを所有していて、投稿は自分しかできないが、閲覧は誰でも出来る」メモアプリが実現できます。

このように、セキュリティルールとユーザーIDを組み合わせることで、簡単にアカウント機能を持ったWebアプリを作成できます。