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?

Go でサクッと Google OAuth 2.0

Posted at

はじめに

Go で Google OAuth 2.0 のクライアントアプリケーションを実装します。

今回のシナリオは、Google アカウントでログインし、Google リソースサーバーからユーザー情報を取得します。
シーケンスは以下の通りです。

※1: 今回は導線無しで、直リンクでアクセスします

手順

1. Google Cloud の設定

詳細な認証情報の作成は省略します。Google Cloud Console のドキュメントをご参照ください。

主要な設定は以下の通りです。

  • アプリケーションの種類: ウェブアプリケーション
  • 承認済みのリダイレクト URI: http://your_server_url/callback

認証情報作成後、クライアント ID とクライアントシークレットを取得しておきます。

2. セットアップ

2-1. Go 関連(ライブラリは OAuth2.0 関連のみ記載)

go 1.24.0

require (
  golang.org/x/oauth2 v0.19.0
)

2-2. 環境変数

1 で取得したクライアント ID とクライアントシークレットを環境変数に設定します。
REDIRECT_URL は、1 で設定した「承認済みのリダイレクト URI」と同じ値を設定します。
また、今回はユーザー情報を取得するためのスコープのみ設定します。

GOOGLE_CLIENT_ID=your_client_id
GOOGLE_CLIENT_SECRET=your_client_secret
REDIRECT_URL=http://your_server_url/callback
SCOPES="openid,email,profile"

3. 実装

3-1. OAuth2.0 クライアントの資格情報

import (
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
)

// 環境変数の値が入る
oauth2Config := &oauth2.Config{
	ClientID:     clientID,
	ClientSecret: clientSecret,
	RedirectURL:  redirectURL,
	Scopes:       scopes,
	Endpoint:     google.Endpoint,
}

3-2. State

CSRF 攻撃を防ぐために使用するランダム(他者が推測困難)な文字列です。
具体的な使用方法は、3-4 と、3-5 で説明します。

state := uuid.New().String()

3-3. PKCE

認可コード傍受攻撃を防ぐために使用します。仕様は RFC7636 に準拠しています。
具体的な使用方法は、3-4 と、3-5 で説明します。


func randomAlphaNumericString(length int) (string, error) {
  chars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"
  bytes := make([]byte, length)
  if _, err := rand.Read(bytes); err != nil {
    return "", fmt.Errorf("failed to generate random bytes: %w", err)
  }

  result := ""
  for _, b := range bytes {
    result += string(chars[b%byte(len(chars))])
  }

  return result, nil
}

codeVerifier, err := randomAlphaNumericString(43)
if err != nil {
	return nil, fmt.Errorf("failed to generate code verifier: %w", err)
}

hash := sha256.Sum256([]byte(codeVerifier))
codeChallenge := base64.RawURLEncoding.EncodeToString(hash[:])

3-4. /authorize エンドポイント

認可リクエストを作成します。
3-2 で生成した state と、3-3 で生成した codeChallenge をクエリパラメータとして送信します。

当該エンドポイントにアクセスすると、Google 認可サーバーにリダイレクトされます。認可サーバーは認可画面を表示し、リソースオーナーに認可を要求します。

http.HandleFunc("/authorize", func(w http.ResponseWriter, r *http.Request) {
  http.Redirect(w, r, oauth2Config.AuthCodeURL(
    state,
    oauth2.SetAuthURLParam("code_challenge", codeChallenge),
    oauth2.SetAuthURLParam("code_challenge_method", "S256"),
  ), http.StatusFound)
})

3-5. /callback エンドポイント

3-4 でリソースオーナーが認可を行った後、認可サーバーから当該エンドポイントにリダイレクトされます。
ここでは state の検証とトークンの取得を行います。

http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
  // State の検証とトークンの取得
})
3-5-1. State の検証

3-4 で送信した state と、認可サーバーから返却される state を比較することで、CSRF 攻撃を検出します。値が一致しない場合は、処理の過程で攻撃者に介入された可能性があるため、エラーとして処理します。

receivedState := r.URL.Query().Get("state")
if state != receivedState {
  http.Error(w, "invalid state", http.StatusBadRequest)
  return
}
3-5-2. トークンの取得

認可サーバーから返却された認可コード(code)を使用して、トークンを取得します。

また、この時、3-3 で生成した codeVerifier も送信します。
これにより、認可サーバー側でトークン要求の正当性を確認してくれます。
具体的には、認可サーバー側で codeVerifier をハッシュ化した値と、3-4 で事前に送信しておいた codeChallenge を比較し、認可要求とトークン要求が同一の要求元であることを確認します。

code := r.URL.Query().Get("code")
if code == "" {
  http.Error(w, "code not found", http.StatusBadRequest)
  return
}

token, err := oauth2Config.Exchange(
  ctx,
  code,
  oauth2.SetAuthURLParam("code_verifier", codeVerifier),
)
if err != nil {
  http.Error(w, fmt.Sprintf("failed to exchange token: %v", err), http.StatusInternalServerError)
  return
}

成功すれば、トークンを使用して Google API を呼び出すことができます。

3-6. ユーザー情報の取得

トークンを使用してユーザー情報を取得します。

client := oauth2Config.Client(ctx, token)
res, err := client.Get("https://www.googleapis.com/oauth2/v3/userinfo")
if err != nil {
  return nil, fmt.Errorf("failed to get user info: %v", err)
}

ユーザー情報が取得できれば完了です。

リファレンス

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?