FJCT_ニフクラ mobile backend(mBaaS)お役立ちブログ

スマホアプリ開発にニフクラ mobile backend(mBaaS)。アプリ開発に役立つ情報をおとどけ!

mBaaSで多要素認証(ワンタイムパスワード)を使う

f:id:mbaasdevrel:20190802215347p:plain

スマートフォンに重要なデータが保存されるのに伴って、セキュリティ意識の高まりが強まっています。その際利用されるのが多要素認証と呼ばれるものです。ID/パスワードの認証に加えて、SMSやワンタイムパスワード、セキュリティキーなどを使って認証を行います。

mBaaSではデフォルトでは追加認証要素には対応していません。そこで、スクリプト機能を使ってワンタイムパスワードを使った認証に対応してみたいと思います。

要件

  • mBaaSのID/パスワード認証を使います
  • 認証自体を制限するのではなく、特定の重要な機能(決済など)を制限する目的とします

mBaaSでデフォルトで提供しているID/パスワード認証を使いますので、認証自体は多要素認証を使わずに完了してしまいます。そこで、決済やクレジットカード情報の登録など重要な機能を使う場合にワンタイムパスワードを利用する想定で開発します。

QRコードを生成する

ワンタイムパスワードを使う際には、専用のアプリ(Google Authenticator、Authyなど)でQRコードを読み込んで実行するでしょう。まず、このQRコードを生成します。

セキュリティコードの生成

QRコードではユーザごとにランダムなセキュリティコードを用います。そのコードを生成するのに使えるのが speakeasy というライブラリです。

const speakeasy = require('speakeasy');

そして、キーの名前(サービス名)や発行者、文字列の長さを指定してセキュリティキーを生成します。

const secret = speakeasy.generateSecret({ name, issuer, length });

セキュリティキーを生成したら、そのURLを取得してQRコードとして表示します。

const url = speakeasy.otpauthURL({
  secret: secret,
  label: encodeURIComponent(name),
  issuer: issuer
});
new QRCode(document.getElementById("qrcode"), url);

これでQRコードが生成されます。

セキュリティコードを mBaaSに保存する

上記のコードで生成したセキュリティコードは、検証にも用います。そこで、コードをmBaaSに保存しておきます。ユーザからは書き込み(更新)できるように、検証時のために Admin グループに入っているユーザに読み込み権限を付けておきます。

const user = ncmb.User.getCurrentUser();
if (!user) throw new Error("You're not logged in");
const Secret = ncmb.DataStore('Secret');
try {
  const secret = await Secret
    .equalTo('userId', user.objectId)
    .fetch();
  if (secret.objectId) {
    return secret.secret;
  }
} catch (e) {
  throw e;
}
const secret = new Secret;
const string = speakeasy.generateSecret({ name, issuer, length });
const acl = new ncmb.Acl;
acl
  .setUserReadAccess(user, true)
  .setUserWriteAccess(user, true)
  .setRoleReadAccess('Admin', true);
await secret
  .set('secret', string.ascii)
  .set('userId', user.objectId)
  .set('acl', acl)
  .save();

検証する

ここまでのコードでコード管理用アプリに登録まで完了します。次に、コードを入力して検証をします。

スクリプト

サーバでも speakeasy を使います。

const speakeasy = require('speakeasy');

まず管理ユーザでログインします。

const userName = 'admin';
const password = 'admin';
await ncmb.User.login(userName, password);

次にデータストアに保存されているセキュリティキーを取得します。実行時にはクライアントからユーザIDを送信してもらい、それを使ってデータを検索します。

const Secret = ncmb.DataStore('Secret');
const secret = await Secret
  .equalTo('userId', req.body.user_id)
  .fetch();
if (!secret.objectId) {
  return res.status(404).json({message: `User not found. ${req.body.user_id}`});
}

そして検証します。 code というのがユーザが入力したワンタイムパスワードです。

const code = req.body.code;
const verified = speakeasy.totp.verify({
  secret: secret.secret,
  encoding: 'ascii',
  token: code
});

検証結果に応じてレスポンスを返します。

if (verified) {
  return res.status(200).json({message: 'Verified.'});
} else {
  return res.status(401).json({message: 'Failed.'});
}

クライアントの実装

スクリプトが totp.js とした場合、クライアントでは以下のようなコードで検証を実行します。

document.getElementById("verify").onclick = async (e) => {
  const user = ncmb.User.getCurrentUser();
  if (!user) throw new Error("You're not logged in");
  const code = document.getElementById("code").value;
  try {
    const response = await ncmb.Script
      .data({
        user_id: user.objectId,
        code: code
      }) 
      .exec('POST', 'totp.js');
    document.getElementById("result").innerHTML = '検証OK';
  } catch (e) {
    document.getElementById("result").innerHTML = '検証NG';
  }
}

まとめ

今回のコードはNCMBMania/TOTP-Demo: mBaaSでワンタイムパスワードを実現するデモです。にあります。実装時の参考にしてください。アプリのセキュリティは強く求められるようになっています。サーバのセキュリティはmBaaSが提供できますが、アプリのUI/UXでも工夫が必要でしょう。ぜひ皆さんのアプリ開発で役立ててください。

中津川 篤司

中津川 篤司

NCMBエヴァンジェリスト。プログラマ、エンジニアとしていくつかの企業で働き、28歳のときに独立。 2004年、まだ情報が少なかったオープンソースソフトの技術ブログ「MOONGIFT」を開設し、毎日情報を発信している。2013年に法人化、ビジネスとエンジニアを結ぶDXエージェンシー「DevRel」活動をスタート。