Auth0でのMQTTによるデバイスの認証と認可
MQTTは、他のシステムと通信するためのデバイスによく使用される軽量なプロトコルで、publish/subscribe(パブリッシュ/サブスクライブ)型のメッセージングプラットフォーム向けに設計されています。詳細については、WikipediaのMQTTをお読みください。
一般には3つのコンポーネントから成り立ちます。
メッセージの
publisher
(パブリッシャー)メッセージの
subscriber
(サブスクライバー)メッセージを仲介する
broker
(ブローカー)
メッセージが関連付けられているtopics
(トピック)(チャネル
またはサブジェクト
とも呼ばれる)の)概念があります。トピックはパブリッシャーとサブスクライバー間のメッセージをルーティングします。
MQTTプロトコルは、usernames
およびpasswords
に基づいて基本的な認証メカニズムをサポートします。これらの資格情報は、CONNECT
メッセージとともに送信されます。
この記事では、nodejsベースのMQTTブローカー(moscaおよびAuth0)間の統合について説明します。この例では、Auth0を使ってpublishers
およびsubscribers
のブローカーに対する認証を行い、メッセージのルーティングを認可します。

ソリューションのコンポーネント
ブローカー
moscaはホストにとっては簡単で、他のサーバーに埋め込むことができます。このサンプルの目的上、moscaサーバーを自己ホストします。
var mosca = require('mosca')
var Auth0Mosca = require('auth0mosca');
var settings = {
port: 9999,
};
//'Thermostats' is a Database connection where all devices are registered.
var auth0 = new Auth0Mosca('https://eugeniop.auth0.com', '{Your Auth0 ClientID}', '{Your Auth0 Client Secret}','Thermostats');
//Setup the Mosca server
var server = new mosca.Server(settings);
//Wire up authentication & authorization to mosca
server.authenticate = auth0.authenticateWithCredentials();
server.authorizePublish = auth0.authorizePublish();
server.authorizeSubscribe = auth0.authorizeSubscribe();
server.on('ready', setup);
// Fired when the mqtt server is ready
function setup() {
console.log('Mosca server is up and running');
}
server.on('clientConnected', function(client) {
console.log('New connection: ', client.id );
});
Was this helpful?
これによって、ポート9999でMQTTメッセージをリッスンするサーバーが作成されます。moscaでは、操作の認証と認可に使用する3つの関数をオーバーライドすることができます。
このサンプルでは、非常に単純なモジュールauth0mosca
を使ってこれらの関数を実行します。Auth0はmoscaに結びついています。
Auth0Moscaモジュール
この小さなモジュールには、moscaで使用される4つの関数(authenticateWithCredentials
、authenticateWithJWT
、authorizePublish
、authorizeSubscribe
)があります。
var request = require('request');
var jwt = require('jsonwebtoken');
function Auth0Mosca(auth0Namespace, clientId, clientSecret, connection)
{
this.auth0Namespace = auth0Namespace;
this.connection = connection;
this.clientId = clientId;
this.clientSecret = clientSecret;
}
Auth0Mosca.prototype.authenticateWithJWT = function(){
var self = this;
return function(client, username, password, callback) {
if( username !== 'JWT' ) { return callback("Invalid Credentials", false); }
// console.log('Password:'+password);
jwt.verify(password, self.clientSecret, function(err,profile){
if( err ) { return callback("Error getting UserInfo", false); }
console.log("Authenticated client " + profile.user_id);
console.log(profile.topics);
client.deviceProfile = profile;
return callback(null, true);
});
}
}
Auth0Mosca.prototype.authenticateWithCredentials = function(){
var self = this;
return function(client, username, password, callback) {
var data = {
client_id: self.clientId, // {client-name}
username: username.toString(),
password: password.toString(),
connection: self.connection,
grant_type: "password",
scope: 'openid name email' //Details: https:///scopes
};
request.post({
headers: {
"Content-type": "application/json"
},
url: self.auth0Namespace + '/oauth/ro',
body: JSON.stringify(data)
}, function(e,r,b){
if(e){
console.log('Error in Authentication');
return callback(e,false);
}
var r = JSON.parse(b);
if( r.error ) { return callback( r, false); }
jwt.verify(r.id_token, self.clientSecret, function(err,profile){
if( err ) { return callback("Error getting UserInfo", false); }
client.deviceProfile = profile;
return callback(null, true);
});
});
}
}
Auth0Mosca.prototype.authorizePublish = function() {
return function (client, topic, payload, callback) {
callback(null, client.deviceProfile && client.deviceProfile.topics && client.deviceProfile.topics.indexOf(topic) > -1);
}
}
Auth0Mosca.prototype.authorizeSubscribe = function() {
return function(client, topic, callback) {
callback(null, client.deviceProfile && client.deviceProfile.topics && client.deviceProfile.topics.indexOf(topic) > -1);
}
module.exports = Auth0Mosca;
Was this helpful?
authenticateWithCredentials
はOAuth2リソース所有者のパスワード資格情報付与を使って、ブローカーおよびブローカーへのすべての接続を認証します。publisher
またはsubscriber
がCONNECTメッセージをブローカーに送信するたびに、authenticate
関数が呼び出されます。この関数でAuth0エンドポイントを呼び出し、デバイスのusername
/password
を転送します。Auth0は、これをアカウントストア(コード内の最初のrequest.post
)と比較して検証します。検証に成功した場合、JSON Web Token(JWT)を検証および解析してデバイスのプロファイルを取得し、subscriber
またはpublisher
を表すclient
オブジェクトに追加します。これは、jwt.verify
呼び出しで行われます。
慣例により、ブローカーに接続されたデバイスはすべて、Auth0にアカウントがあります。
デバイスプロファイルにもtopics
プロパティがあります。これは、この特定のデバイスが許可されたすべてのトピックを含む配列です。上のスクリーンショットでは、thermostat-1a
はtemperature
およびconfig
トピックへのパブリッシュ(またはサブスクライブ)が許可されます。
authorizePublish
関数とauthorizeSubscribe
関数は、要求された特定のトピックがこのリストに載っているかどうかを確認するだけです。
authenticateWithJWT
は、password
フィールドでJWTを想定します。この場合の流れは若干異なります。
パブリッシャーとサブスクライバーがトークンを取得する
mosca
に接続してJWTを送信するmosca
がJWTを検証するメッセージが送信されサブスクライバーに再送信される

パブリッシャーとサブスクライバーは何らかの方法でJWTを取得します。ブローカーはAuth0と通信する必要はなくなりました。JWTは自己完結型のアーティファクトであり、署名に使用するシークレットで検証することができます。
パブリッシャー
このサンプルの場合、パブリッシャーはmqtt
モジュールを使用する単純なnodejsプログラムであり、正しい資格情報を追加します。
var mqtt = require('mqtt')
, host = 'localhost'
, port = '9999';
var settings = {
keepalive: 1000,
protocolId: 'MQIsdp',
protocolVersion: 3,
clientId: 'Thermostat 1a',
username:'thermostat-1a',
password:'the password'
}
// client connection
var client = mqtt.createClient(port, host, settings);
setInterval(sendTemperature, 2000, client);
function sendTemperature(client){
var t = {
T: Math.random() * 100,
Units: "C"
};
client.publish('temperature', JSON.stringify(t));
}
Was this helpful?
もちろん、username
とpassword
は、Auth0に保存されているものに一致しなければなりません。
サブスクライバー
サブスクライバーはパブリッシャーに非常に似ています。
var mqtt = require('mqtt')
, host = 'localhost'
, port = '9999';
var settings = {
keepalive: 1000,
protocolId: 'MQIsdp',
protocolVersion: 3,
clientId: 'Reader-X1',
username:'reader-X1',
password:'the password'
}
// client connection
var client = mqtt.createClient(port, host, settings);
client.subscribe('temperature');
client.on('message', function(topic, message) {
if(topic ==='temperature')
{
console.log('New reading', message);
}
});
Was this helpful?
Summary(概要)
ルールを通じて非常に簡単に実装することができます。柔軟なAuth0 Profileが、任意のアーティファクト(この例のtopics
など)をサポートするためにどのように拡張できるかについても説明しています。
Rules(ルール)の詳細については、「Auth0 Rules」を確認してください。
資格情報(username
/password
)を安全でないネットワークを介して送信することは絶対に避けてください。メッセージ内容が公開されることを防ぐTransport Level Securityを提供する他の実装もあります。1つの例として、moscaはTLSをサポートします。すべてのトラフィックが閉域網を通る場合を除き、運用環境への導入ではこれが優先されるでしょう。
謝辞
本記事の編集にご協力いただき、素晴らしいmoscaを構築していただいたMatteo Collina氏に感謝の意を表します。