GoaにおけるBasic認証
GoaのAPIでBasic認証を実装する方法を学びます
Basic認証は、HTTPプロトコルに組み込まれたシンプルな認証スキームです。最も単純な認証形式の 1つですが、特に内部APIや開発環境で広く使用されています。
Basic認証の仕組み
Basic認証を使用する場合:
- クライアントはユーザー名とパスワードをコロンで結合します(username:password)
- この文字列はbase64でエンコードされます
- エンコードされた文字列はAuthorizationヘッダーで送信されます:
Authorization: Basic base64(username:password)
GoaでのBasic認証の実装
1. セキュリティスキームの定義
まず、設計パッケージでBasic認証のセキュリティスキームを定義します:
package design
import (
. "goa.design/goa/v3/dsl"
)
// BasicAuthはセキュリティスキームを定義
var BasicAuth = BasicAuthSecurity("basic", func() {
Description("APIにアクセスするにはユーザー名とパスワードを使用してください")
})
2. セキュリティスキームの適用
Basic認証は異なるレベルで適用できます:
// APIレベル - すべてのサービスとメソッドに適用
var _ = API("secure_api", func() {
Security(BasicAuth)
})
// サービスレベル - サービス内のすべてのメソッドに適用
var _ = Service("secure_service", func() {
Security(BasicAuth)
})
// メソッドレベル - このメソッドにのみ適用
Method("secure_method", func() {
Security(BasicAuth)
})
3. ペイロードの定義
Basic認証を使用するメソッドでは、ユーザー名とパスワードのフィールドを含むペイロードを 定義する必要があります:
Method("login", func() {
Security(BasicAuth)
Payload(func() {
// これらの特別なDSL関数はGoaによって認識されます
Username("username", String, "認証用のユーザー名")
Password("password", String, "認証用のパスワード")
Required("username", "password")
})
Result(String)
HTTP(func() {
POST("/login")
// Responseは認証成功後の動作を定義します
Response(StatusOK)
})
})
4. セキュリティハンドラーの実装
Goaがコードを生成したら、セキュリティハンドラーを実装する必要があります。 以下は例です:
// SecurityBasicAuthFuncはBasic認証の認可ロジックを実装します
func (s *service) BasicAuth(ctx context.Context, user, pass string) (context.Context, error) {
// ここで認証ロジックを実装
if user == "admin" && pass == "secret" {
// 認証成功
return ctx, nil
}
// 認証失敗
return ctx, basic.Unauthorized("無効な認証情報")
}
Basic認証のベストプラクティス
常にHTTPSを使用 Basic認証は認証情報をbase64エンコードして送信します(暗号化ではありません)。 転送中の認証情報を保護するために、常にHTTPSを使用してください。
安全なパスワード保存
- パスワードを平文で保存しない
- 強力なハッシュアルゴリズム(bcryptなど)を使用
- パスワードハッシュにソルトを追加
- 安全なパスワード管理ライブラリの使用を検討
レート制限 ブルートフォース攻撃を防ぐためにレート制限を実装:
var _ = Service("secure_service", func() { Security(BasicAuth) // レート制限のアノテーションを追加 Meta("ratelimit:limit", "60") Meta("ratelimit:window", "1m") })エラーメッセージ ユーザー名とパスワードのどちらが間違っているかを明らかにしないでください。 一般的なメッセージを使用:
return ctx, basic.Unauthorized("無効な認証情報")ログ記録 認証試行をログに記録しますが、パスワードは決して記録しないでください:
func (s *service) BasicAuth(ctx context.Context, user, pass string) (context.Context, error) { // 良い例:ユーザー名と結果のみをログに記録 log.Printf("認証試行 ユーザー: %s", user) // 悪い例:これは絶対にしないでください // log.Printf("パスワード試行: %s", pass) }
実装例
以下は、GoaサービスでBasic認証を実装する完全な例です:
package design
import (
. "goa.design/goa/v3/dsl"
)
var BasicAuth = BasicAuthSecurity("basic", func() {
Description("APIアクセス用のBasic認証")
})
var _ = API("secure_api", func() {
Title("セキュアAPIの例")
Description("Basic認証を示すAPI")
// デフォルトですべてのエンドポイントにBasic認証を適用
Security(BasicAuth)
})
var _ = Service("secure_service", func() {
Description("認証を必要とするセキュアなサービス")
Method("getData", func() {
Description("保護されたデータを取得")
// セキュリティ要件を定義
Security(BasicAuth)
// ペイロードを定義(認証情報は自動的に追加されます)
Payload(func() {
// ここに追加のペイロードフィールドを追加
Field(1, "query", String, "検索クエリ")
})
// 結果を定義
Result(ArrayOf(String))
// HTTPトランスポートを定義
HTTP(func() {
GET("/data")
Response(StatusOK)
Response(StatusUnauthorized, func() {
Description("無効な認証情報")
})
})
})
// パブリックエンドポイントの例
Method("health", func() {
Description("ヘルスチェックエンドポイント")
NoSecurity()
Result(String)
HTTP(func() {
GET("/health")
})
})
})
生成されるコード
Goaは、Basic認証のために以下のコンポーネントを生成します:
セキュリティタイプ
- 認証情報のタイプ
- 認証失敗のエラータイプ
ミドルウェア
- リクエストから認証情報を抽出
- セキュリティハンドラーを呼び出し
- 認証エラーを処理
OpenAPIドキュメント
- セキュリティ要件を文書化
- 必要なフィールドを表示
- エラーレスポンスを文書化
一般的な問題と解決策
1. 認証情報が送信されない
認証情報が送信されない場合、以下を確認してください:
Authorizationヘッダーのフォーマット- Base64エンコーディング
- 特殊文字のURLエンコーディング
2. 常に認証エラーになる
一般的な原因:
- 設計での
Security()の欠落 - セキュリティハンドラーの実装が不正確
- ミドルウェアの順序の問題
3. CORSの問題
ブラウザベースのクライアントの場合、適切なCORS設定を確保してください:
var _ = Service("secure_service", func() {