了解です。
やることは:
Google Authenticator 用の TOTP シークレット生成
otpauth URL を作成
QRコード生成
シークレット情報を MySQL に保存
コード検証API
構成はシンプルな HTTP サーバ例で示します。
使用ライブラリ:
github.com/pquerna/otp
github.com/pquerna/otp/totp
github.com/skip2/go-qrcode
github.com/go-sql-driver/mysql
1. go.mod
go mod init example.com/mfago get github.com/pquerna/otpgo get github.com/pquerna/otp/totpgo get github.com/skip2/go-qrcodego get github.com/go-sql-driver/mysql
2. MySQL テーブル
CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(255) UNIQUE NOT NULL, totp_secret VARCHAR(255), created_at DATETIME DEFAULT CURRENT_TIMESTAMP);
※ QRコード自体は保存不要です。
保存するのは secret です。
3. main.go
package mainimport ( "database/sql" "encoding/base64" "fmt" "log" "net/http" "time" "github.com/pquerna/otp" "github.com/pquerna/otp/totp" "github.com/skip2/go-qrcode" _ "github.com/go-sql-driver/mysql")var db *sql.DBfunc main() { var err error db, err = sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname") if err != nil { log.Fatal(err) } http.HandleFunc("/register", registerHandler) http.HandleFunc("/verify", verifyHandler) log.Println("server start :8080") http.ListenAndServe(":8080", nil)}func registerHandler(w http.ResponseWriter, r *http.Request) { username := r.URL.Query().Get("username") if username == "" { http.Error(w, "username required", 400) return } // TOTP secret生成 key, err := totp.Generate(totp.GenerateOpts{ Issuer: "MyApp", AccountName: username, }) if err != nil { http.Error(w, "failed to generate", 500) return } // DB保存 _, err = db.Exec("INSERT INTO users(username, totp_secret) VALUES(?, ?)", username, key.Secret()) if err != nil { http.Error(w, "db error", 500) return } // QRコード生成 png, err := qrcode.Encode(key.URL(), qrcode.Medium, 256) if err != nil { http.Error(w, "qr error", 500) return } base64QR := base64.StdEncoding.EncodeToString(png) w.Header().Set("Content-Type", "text/html") fmt.Fprintf(w, ` <h1>Scan this QR with Google Authenticator</h1> <img src="data:image/png;base64,%s"> <p>Secret: %s</p> `, base64QR, key.Secret())}func verifyHandler(w http.ResponseWriter, r *http.Request) { username := r.URL.Query().Get("username") code := r.URL.Query().Get("code") var secret string err := db.QueryRow("SELECT totp_secret FROM users WHERE username = ?", username). Scan(&secret) if err != nil { http.Error(w, "user not found", 404) return } valid := totp.Validate(code, secret) if valid { fmt.Fprintln(w, "OK") } else { fmt.Fprintln(w, "INVALID") }}
動作
登録
http://localhost:8080/register?username=testuser
→ QRコード表示
→ Google Authenticatorでスキャン
検証
http://localhost:8080/verify?username=testuser&code=123456
実運用での注意
secretは平文保存しない(AES暗号化推奨)
HTTPS必須
レート制限
再登録制御
リカバリコード実装
セキュリティ強化版の設計例
secretをAES-GCMで暗号化して保存
失敗回数制限
ログ記録
時間スキュー考慮
この記事へのコメント