はじめに
2段階認証と言うと筆者の狭い見識では仮想通貨関連のアプリケーションでよく使われているという印象だったんですが、最近では様々なアプリケーションに使用されている(Qiitaさんとか、Gmailとか)なと思ったのと、直近で触ってるプロジェクトで導入することになったのでメモがてらその流れを記しておこうと思います。
結論から言うと導入自体は非常に簡単でビックリしました。
前提
- Google Authenticatorがスマホにインストールされている事
動作確認した環境
- 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側でする必要があるので、その処理の紹介を。
{
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で認証は取れるんですが…………
なんか…………………
物足りないですよね……………
それもそのはず。
上記画像の通りで、サービス名が無いし、下には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と同じ値にしてあげてください。
これで一通りやってみると…、
できましたね!
やはりかの有名企業と同じアプリ内で名を連ねるのはなんか………こう…………………ほっこりしますね☺️
あと、個人的にハマったことがあってこのnameとlabelですが、上記のようにSecret Key作成時とQRコード作成時に同じ値を入れてあげないとSecret Keyが同じでも認証コードが一致することはありません。(当たり前と言えば当たり前なんですが…)