Open7

echo・認証まわり

engineer rebornengineer reborn

認証

  • セッションパターン
  • jwtパターン
  • csrf
  • ドメイン
  • SSO(シングルサインオンの場合)

echo

engineer rebornengineer reborn

セッションでの認証

  1. ユーザーがログイン情報(ID/パスワード)を送信
  2. サーバーが認証に成功
  3. ランダムなセッションIDを生成
  4. セッションID と user_id をサーバー側(DB/Redis)に保存
    5. DBには、セッションIDをハッシュ化して保存
  5. セッションID をクライアントに Set-Cookie で返す
  6. クライアントは以降のリクエストで Cookie にセッションIDを含めて送信
  7. サーバーは受け取ったセッションIDから user_id を特定し認証を通す
    9. 受け取ったセッションIDをハッシュ化してDB検索
// Cookie から「生の session_id(例: abc123xyz)」を取得
rawSessionID := getCookie(c.Request())

// 同じ方法でハッシュ化して DB のキーに使う
hashed := hashSessionID(rawSessionID)

// DB で user_id を検索
userID := db.GetUserIDBySessionHash(hashed)
engineer rebornengineer reborn

実装参考

https://zenn.dev/ktkthude/articles/dfe630ad26b342

jwt

  1. ユーザーがログイン情報(ID/パスワード)を送信
  2. サーバーが認証に成功
  3. user_id や exp(有効期限)などを含む JWT を生成し、署名付きで発行
  4. JWT をクライアントにレスポンスで返す(Set-Cookie や JSON などで)
  5. クライアントは以降のリクエストで JWT を Authorization ヘッダーに含めて送信
  6. サーバーは署名を検証してトークンが改ざんされていないかチェック
  7. トークン内の user_id などの情報を使って認証を通す(サーバーは状態を保持しない)

サンプルコード

  • token作成
    • 共通鍵なので、鍵は1つしかない
      • secret managerなどで管理されているはず
    • NewWithClaims
      • アルゴリズムの指定して、データを作成
      • tokenを作成
        • SignedStringメソッド、署名できる
          • base64の文字列, errorを返却
  • token parse
    • jwt.Parse
      • tokenを受け取り、SigningMethodHS256を指定
      • secretも指定 hmacSampleSecret
      • parsedToken.Claims (interface型)を返却する
package main

import (
	"fmt"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

// トークン作成
// https://pkg.go.dev/github.com/golang-jwt/jwt/v5#example-New-Hmac

// トークン パース
// https://pkg.go.dev/github.com/golang-jwt/jwt/v5#example-Parse-Hmac
func main() {

	// 共通鍵方式

	// secrer managerなどで暗号化されてることが多い
	hmacSampleSecret := []byte("your-secret")

	// HS256 = HMAC + SHA-256
	// HMAC 共通化ぎ方式
	// SHA-256 署名方式 サーバーサイドだけで完結できる

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"foo": "bar",
		"nbf": time.Date(2025, 06, 16, 12, 0, 0, 0, time.UTC).Unix(),
	})

	fmt.Println("token", token)

	tokenString, err := token.SignedString(hmacSampleSecret)

	fmt.Println(tokenString, err)

	// トークンをパース
	parsedToken, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		// hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
		return hmacSampleSecret, nil
	}, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}))

	fmt.Println("")
	fmt.Println("parsedToken", parsedToken)
	fmt.Println("parsedToken.Claims", parsedToken.Claims)

	if claims, ok := parsedToken.Claims.(jwt.MapClaims); ok {
		fmt.Println(claims)
		fmt.Println(claims["foo"], claims["nbf"])
	} else {
		fmt.Println(err)
	}

}

engineer rebornengineer reborn

認証まわりで必要な知識

認証まわりで理解必要な知識

  1. same site オリジン
    cookieが送れる制限の設定
    strit , lax , none
  2. cookieの設定

http only true => 自動送信
http only false => jsからも取れる => csrfで
3. ⭐️ブラウザからクッキーにjsでアクセスする仕様

同じドメインから送られたcookieでないとjsでcookieは取れない

  1. csrf

  2. 認証を送る時

  • cookieで
  • リクエストヘッダーで
    • 手動で authentication
  1. cors
    ブラウザが他のオリジンのAPIにリクエストをアクセスして良いかサーバーに聞いて判断する
  • サーバー側で、Access-Control-Allow-Originの設定をする
engineer rebornengineer reborn

csrf token && jwtの運用一例

14.24分

  • 前提
    • ⭐️/get csrfはcorsのホワイトリストで制限してるのでトークを攻撃者は保持できない
      • ホワイトリスト、正規のサイトのipアドレスしかリクエストを受けつえないとか
    • 対象リクエストは、更新系、POST,PUT,DELETE
  • フロー
    • ロードされるたびに、csrfトークンを発行
      • トークンはjsonで
        • axiosなどのheaderにセットする。
          • 都度初期化されるので、セットすれば送れる
      • トークン発行のsecretはhttp onlyで
    • api側へリクエスト
      • credentials trueで secretを送る
      • headerにはセットcsrdをセットしてるので送れる
  • jwt
    • ログインしたら access tokenを http onlyで返却
    • リクエストのたびに、署名チェックで認証をOKとする
    • sessionや contextにユーザーidなどをセットする
    • ⭐️これだけだと cookie自動付与でなりすましができてしまう
engineer rebornengineer reborn

echo

  • go echo
  • go api 技術書店 go中級者に含まれるかも
  • [] ミドルウェアの使い方
    • ロガー zerolog
      • work/tracking_price/backend_app
      • バリデーション
      • work/tracking_price/backend_app
      • [] context
      • [] csrf
      • [] errorhadler