0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ワンタイムパスワードのサンプルを作成してみた

Posted at

概要

多要素認証を実装する時OTP(ワンタイムパスワード)を使用したい.
とりあえずゴリゴリ実装してみたのでその記録

とりあえずソースコード

自分の過去の資料を見てもらえればわかるかもしれないが,React,TypeScriptで実装している.
自分もよくわかってないので,説明不足なところはご勘弁.

たためるようにしておきました.
import * as speakeasy from 'speakeasy';
import QRCode from 'qrcode';
import React, { useEffect, useState } from 'react';

function OTPTest() {
    const [QRCodeDisplay, setQRCodeDisplay] = useState('');
    const [PassWord, setPassWord] = useState('');
    const [verificationResult, setVerificationResult] = useState('認証実行前');

    const test = async () => {
        console.log('test実行中...');
        const secret = generateSecret();
        try {
            await generateQRCodeASCII(secret);
            console.log('QRコードが正常に生成されました。');
        } catch (error) {
            console.error('QRコード生成中にエラーが発生しました:', error);
        }
    };

    const generateSecret = () => {
        const secret = speakeasy.generateSecret({ length: 20 });
        // DBに登録する必要がある. 以下はテスト用のダミーデータ
        const test_mode = true;
        if(test_mode) {
            return {
                ascii: '!{[L.wsBzx2}f}%?UqZW', // 基本これしか使わない
                hex: '217b5b4c2e7773427a78327d667d253f55715a57',
                base32: 'EF5VWTBOO5ZUE6TYGJ6WM7JFH5KXCWSX',
                otpauth_url: 'otpauth://totp/SecretKey?secret=EF5VWTBOO5ZUE6TYGJ6WM7JFH5KXCWSX'
            };
        }
        return secret;
    };

    const getSecret = (userId:string) => {
        // DBからsecretを取得する
        // 以下はテスト用のダミーデータ
        return {
            ascii: '!{[L.wsBzx2}f}%?UqZW', // 基本これしか使わない
            hex: '217b5b4c2e7773427a78327d667d253f55715a57',
            base32: 'EF5VWTBOO5ZUE6TYGJ6WM7JFH5KXCWSX',
            otpauth_url: 'otpauth://totp/SecretKey?secret=EF5VWTBOO5ZUE6TYGJ6WM7JFH5KXCWSX'
        };
    }

    const generateQRCodeASCII = async (secret: speakeasy.Key) => {
        const otpauthUrl = speakeasy.otpauthURL({
            secret: secret.ascii,
            label: encodeURIComponent('Your App Name:User Email'),
            issuer: 'Your App Name',
            algorithm: 'sha1',
            digits: 6,
            period: 30,
        });

        try {
            const url = await QRCode.toDataURL(otpauthUrl);
            console.log(url);
            setQRCodeDisplay(url); // Update QRCodeDisplay state
            console.log("QRCodeDisplayの表示");
            console.log(QRCodeDisplay);
        } catch (err) {
            console.error('QR Code生成エラー:', err);
        }
    };

    // 二要素認証コードを検証する関数
    const verifyToken = () => {
        // 秘密鍵を取得する(本来はデータベースから取得します)
        const secret = getSecret("dummy"); // この関数は、実際のアプリケーションではユーザーIDに基づいてユーザーの秘密鍵をデータベースから取得する必要があります。

        // speakeasyを使用してトークンの検証を試みる
        const verified = speakeasy.totp.verify({
            secret: secret.ascii,
            encoding: 'ascii',
            token: PassWord,
            window: 1 // ここで指定した範囲内であれば前後のトークンも有効とみなすことができます。
        });

        if (verified) {
            setVerificationResult('認証成功');
        } else {
            setVerificationResult('認証失敗');
        }
    };

    return (
        <div>
            <button onClick={test}>Generate QR Code</button>
            <div>
                {QRCodeDisplay && <img src={QRCodeDisplay} alt="Generated QR Code" style={{ marginTop: '20px' }} />}
            </div>
            <div>
                <input
                    type="text"
                    value={PassWord}
                    onChange={(e) => setPassWord(e.target.value)}
                    placeholder='Enter your 6-digit code here...'
                />
                <button onClick={verifyToken}>Verify Token</button>
            </div>
            <div>{verificationResult}</div>
        </div>
    );
}

export default OTPTest;

表示結果

まずはGenerate QR Codeでワンタイムパスワードの登録を行います.
image.png

下記のQRコードをMicrosoft Authenticatorで読込み,個人端末にてワンタイムパスワードを使用できるように設定します.
image.png

Microsoft Authenticatorでは下記のように表示されます.
image.png

現在書かれているワンタイムパスワードを入力してみます.
image.png

時間が過ぎてしまい,認証に失敗しました.
image.png

今度こそ時間内に入力してみます.
うまくいきました!
image.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?