Edited at

Node.jsとGoogle Authenticatorを使って二段階認証を行う

More than 1 year has passed since last update.


はじめに

2段階認証と言うと筆者の狭い見識では仮想通貨関連のアプリケーションでよく使われているという印象だったんですが、最近では様々なアプリケーションに使用されている(Qiitaさんとか、Gmailとか)なと思ったのと、直近で触ってるプロジェクトで導入することになったのでメモがてらその流れを記しておこうと思います。

結論から言うと導入自体は非常に簡単でビックリしました。


前提


動作確認した環境


  • OS: Ubuntu16.04

  • Node.js: v8.9.4

  • Yarn: v1.5.1


yarn install

$ yarn install speakeasy qrcode


1. speakeasyでSecret Keyの生成

speakeasyとは、2段階認証を行う際のハッシュ値の生成や認証コードが一致するかのチェックなどの手間となる部分をよしなにやってくれるモジュールです。

まずはSecret Keyを生成します。

const speakeasy = require('speakeasy');

const secret = speakeasy.generateSecret({ length: 20 });

console.log(secret.base32);

// FBDXWRCNIBFUA5JKGNKF2SBRGVYUOKJQ

生成すると上記のような値が取得できるので、その値とユーザーとの関連性を持たせる為にユーザー作成時にそのSecret KeyをDBに入れます。(あくまでイメージですが、以下のような形でDB保存します。)

id
username
password
secret_key

1
yuyake0084
hoge
FBDXWRCNIBFUA5JKGNKF2SBRGVYUOKJQ


2. QRコードの画像生成

先ほど取得したsecretという変数にはオブジェクトが入っているのですが、base32というプロパティ以外にもotpauth_urlというプロパティがあるのでそれをもとにQRコードの画像のURLを生成します。

const speakeasy = require('speakeasy');

const QRcode = require('qrcode'); //追記
const secret = speakeasy.generateSecret({ length: 20 });

...

// Secret KeyをDBに入れる処理

...

QRCode.toDataURL(secret.otpauth_url, (err, qrcode) => {
console.log(qrcode); // base64のQRコードの画像パスが入ってきます
});

取得できたQRコードはそのままクライアント側に返して表示してあげればユーザーはQRコードリーダーを介してGoogle Authenticatorにトークンを追加できます。


3. ユーザーが打ち込んだ認証コードが正しいか確認

Google Authticatorでは30秒ごとにパスワードが変更されます。

リアルタイムで変更されていく値とユーザーが入力したパスコードが一致するかどうかの確認をNode.js側でする必要があるので、その処理の紹介を。


POSTリクエスト内容

{

url: '/api/qrcode/1',
method: 'POST',
data: {
authcode: '123456' // ユーザーが入力した値
}
}

const { authcode } = req.body;

const { secret_key } = ... // usersテーブルからidが1のレコードを取得してくる処理

const verified = speakeasy.totp.verify({
secret: secret_key,
encoding: 'base32',
token: authcode
});

console.log(verified); // true or false

speakeasyにはbase32で生成されたSecret Keyと入力した認証コードを比較してBooleanを返してくれるメソッドがあるのでそれがtrueであれば無事認証完了です。なんて簡単。


いいかんじにしたい

上記までのフローでGoogle Authenticatorで認証は取れるんですが…………

Image-uploaded-from-iOS_02.png

なんか…………………

物足りないですよね……………

それもそのはず。

上記画像の通りで、サービス名が無いし、下にはSecretKeyと記載があるのみ。

これでは何のサービスのトークンを保持しているのか分からないのでユーザーには優しくありませんし、どうせやるならそれっぽくしたいのでしましょう。


1. Secret Key作成時にnameとissuerを追記

const secret = speakeasy.generateSecret({

length: 20,
name: 'test.com', // 追記
issuer: 'test' // 追記
});

issuerがサービス名で、nameが『SecretKey』 と書かれてるところに上書きされます。


2. QRコードを生成する際にもlabelとissuerを追記

// 以下のように書き換える

const url = speakeasy.otpauthURL({
secret: secret.ascii,
label: encodeURIComponent('test.com'),
issuer: 'test'
});

QRCode.toDataURL(url, (err, qrcode) => {
console.log(qrcode);
});

labelというプロパティですが、Secret Key生成時のnameと同じ値にしてあげてください。

これで一通りやってみると…、

Image uploaded from iOS (1).png

できましたね!

やはりかの有名企業と同じアプリ内で名を連ねるのはなんか………こう…………………ほっこりしますね☺️

あと、個人的にハマったことがあってこのnameとlabelですが、上記のようにSecret Key作成時とQRコード作成時に同じ値を入れてあげないとSecret Keyが同じでも認証コードが一致することはありません。(当たり前と言えば当たり前なんですが…)


参考にさせてもらった記事