サービスへのMFA認証導入を検討してみる

はじめまして、グッドパッチでサーバーサイドエンジニアをやってる荒木です。

この記事は [Goodpatch Advent Calendar 2024](https://qiita.com/advent-calendar/2024/goodpatch) の 17日目の記事になります。

## はじめに
今回は、Webサービスに対するMFA（Multi-Factor Authentication）の追加について、導入検討とGolangを用いた実装方法の検証を行いました。

### MFAとは？

MFAは、一つのパスワードだけに依存しない安全な認証メカニズムです。ユーザーは以下の要素を組み合わせて認証を行い、不正アクセスを防ぎます。

### 基本的な認証要素

- 知識要素: パスワードやPINコード

- 所有要素: スマートフォン、ICカード、認証アプリ

- 身体要素: 指紋、自分の顔、素顏、声

### 主なMFA認証方式

####  1.  アプリによるトークン（OTP：一時パスワード）
- Google AuthenticatorやMicrosoft Authenticatorなどを利用
- 毎回異なるコードを使用するため、安全性が高い

#### 2. SMSや電子メールを使用したトークン

- ユーザーの電話番号やメールアドレスにコードを送信
- 注意点：SIMスワップやメールハッキングのリスク

#### 3. 指紋や顔認証などの身体認証
- バイオメトリクスを利用
- 「身体がパスワード」とされる高い安全性

#### 4. ハードウェアトークン(端末基盤型)

- USBデバイスやNFCデバイス（例：YubiKey）を使用
- デバイスを利用した物理的な認証


### 実装に向けた検討
既存サービスへのMFA導入方法として、大きく以下の2つのアプローチを検討しました。

#### サードパーティサービス利用する場合

以下の5つのサービスを候補に挙げました。

##### 1.Auth0 (Okta, Inc.)
    概要: クラウドベースのID管理および認証プラットフォームで、MFAを簡単に統合可能。
    主な特徴:
    TOTPやSMS、プッシュ通知を利用したMFA
    OAuth 2.0やOpenID Connectに対応
    カスタマイズ可能なUIと幅広いSDKの提供

##### 2.Microsoft Azure Active Directory (Azure AD)
    概要: Microsoftが提供するIDおよびアクセス管理ソリューション。Azure環境に最適化。
    主な特徴:
    デフォルトでMFA機能を提供
    バイオメトリクス、Microsoft Authenticatorアプリを利用可能
    条件付きアクセスポリシーとの連携が可能

##### 3.Duo Security (Cisco Systems)
    概要: Ciscoが提供するMFA専用ソリューション。エンタープライズ向け。
    主な特徴:
    シンプルな設定と使いやすいインターフェース
    プッシュ通知、SMS、音声通話、U2F対応
    ログイン試行のリアルタイム監視

##### 4.Google Workspace (旧 G Suite)
    概要: Googleのエコシステムで利用できるMFA機能。Google Authenticatorとシームレスに統合。
    主な特徴:
    FIDO認証（セキュリティキー）やスマートフォン通知対応
    シンプルなユーザー体験
    管理者ポリシーでの一元管理が可能

##### 5. Okta
    概要: ID管理プラットフォームで、複数のクラウドサービスやオンプレミス環境との連携が可能。
    主な特徴:
    TOTP、プッシュ通知、セキュリティキーに対応
    条件付きアクセス制御
    簡単なAPI統合とカスタマイズ可能なワークフロー

既存のインフラに合わせた選択となりますが、AWS環境上でのWebサービスでは、特にAuth0が有力な選択肢となるかなと思います。

#### 独自実装する場合

サードパーティサービスを利用すれば幅広い認証方式やセキュリティ面の利点がありますが、必要な認証方式を絞る場合は独自実装も現実的です。ここでは、Golangを用いた実装例を紹介します。

#### Golangによる実装例

1. アプリによるTOTP認証

簡単な認証フローはこちら
![スクリーンショット 2024-12-16 17.06.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/908790/fc261e83-d5c9-d65b-31d4-170fefb5411b.png)

以下のコードはTOTP生成とQRコードの生成を行う例です。

```
package main

import (
	"crypto/rand"
	"fmt"
	"log"

	"github.com/pquerna/otp"
	"github.com/pquerna/otp/totp"
)

func main() {
	// サンプル用ユーザー名
	username := "sampleuser@example.com"

	// TOTPキーを生成
	key, err := totp.Generate(totp.GenerateOpts{
		Issuer:      "SampleApp",
		AccountName: username,
		Period:      30,
		SecretSize:  20,
		Digits:      otp.DigitsSix,
		Algorithm:   otp.AlgorithmSHA1,
		Rand:        rand.Reader,
	})
	if err != nil {
		log.Fatalf("Failed to generate TOTP key: %v", err)
	}

	// キー情報を表示
	fmt.Println("Generated TOTP Key:")
	fmt.Println("URL:", key.URL())
	fmt.Println("Secret:", key.Secret())

	// QRコード画像を生成（例ではファイル保存なし）
	img, err := key.Image(200, 200)
	if err != nil {
		log.Fatalf("Failed to generate QR Code image: %v", err)
	}

    // 発行したTOTPキーをユーザー情報に紐づけて保存！

	// QRコードの生成確認メッセージ
	fmt.Println("QR Code generated successfully!")
	_ = img // imgを利用する場合は適宜保存処理などを追加
}
```
発行QRコードサンプル
![スクリーンショット 2024-12-16 17.22.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/908790/b2a91da0-885d-5553-bbe7-69ca7594b3c3.png)

#### ワンタイムパスワードの検証
以下は入力されたコードを検証するコード例です。

```
// 入力されたワンタイムパスワード
checkCode := 123456

// QRコード生成時に発行したシークレット情報
totpSecret　:= "BASE32ENCODEDSECRET"

// TOTP 検証
valid := totp.Validate(checkCode, totpSecret)
if !valid {
    log.Printf("Invalid TOTP code provided: %s", inputForm.CheckCode)
    c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid TOTP code", "message": "コードが違います。再試行してください。"})
    return
}
```

#### 2. SMSやメールを利用したトークン送信
- 有効期限付きトークンを生成し、外部のSMS送信APIやメールサービスを利用して送信
- メリット：実装がシンプル
- 注意点：利用量に応じたランニングコストに注意


## まとめ
新規サービス開発では、Auth0などのサービスを初期から導入を検討してみるのもありだと思います。ただし、認証情報が外部に保管される場合もあるため、セキュリティポリシーに沿った検討が重要です。

## 参考サイト

https://github.com/pquerna/otp