GoでGmailのログインを実装する

# はじめに
Web開発を行うにあたってログイン機能の追加は多くの人が通る道だと思います。そんなログイン機能の中で最も使われているであろうGoogleのGmailを使用したログイン機能の実装をGo言語で行う方法をまとめた記事です。

# 実装手順
## プロジェクトの作成
まず、Google Cloud Consoleで新しいプロジェクトを作成します。以下の手順に従ってください

### プロジェクト作成画面への遷移
1. <a href=https://console.cloud.google.com/apis/credentials>Google Cloud Console<a/>にアクセスします
2. 画面上部の「プロジェクトの作成」ボタンをクリックします
![スクリーンショット 2024-12-15 20.35.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759397/cfa43072-064a-7873-cce2-ff64ea526b3b.png)

### プロジェクト詳細の設定
1. プロジェクト名を入力します。わかりやすく、プロジェクトの目的を反映した名前をつけましょう
2. 組織に所属している場合は、適切な組織を選択します
![スクリーンショット 2024-12-15 20.37.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759397/52d97731-0ec8-72da-82bf-bb47ee9f1a1e.png)
### プロジェクト作成完了
プロジェクトの作成が完了すると、以下の画面に遷移します。この画面で、新しく作成したプロジェクトが選択された状態になっていることを確認してください。
![スクリーンショット 2024-12-15 20.39.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759397/062e7b8d-2fe3-e908-5a47-68be81c023a6.png)


## OAuth2.0 クライアントの作成
Gmailログイン機能の実装には、OAuth 2.0認証プロトコルを使用します。以下の手順で認証情報を設定します。
### 認証情報の作成
1. 左側のメニューから「認証情報」を選択します
2. 画面上部の「+ 認証情報を作成」をクリックし、「OAuthクライアントID」を選択します
![スクリーンショット 2024-12-15 21.18.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759397/b7fda706-e034-a1d2-76c4-09b3fc5577cc.png)
### クライアント情報の設定
1. アプリケーションの種類で「ウェブアプリケーション」を選択します
2. クライアント名を入力します
3. 承認済みのJavaScript生成元に`http://localhost:8080`を入力します
4. 承認済みのリダイレクトURIに`http://localhost:8080/auth/google/callback`を入力します


これらが入力できるとページ下の作成ボタンをクリックして作成してください。
![スクリーンショット 2024-12-15 21.19.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759397/644a017c-2464-37b0-4a2e-6749c9882c27.png)

### 認証情報の取得
作成が完了すると、`クライアントID`と`クライアントシークレット`が表示されます。**これらは環境変数として後の実装で使用するため、安全に保管してください。**
![スクリーンショット 2024-12-15 21.20.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759397/636cd93a-e477-42a6-9935-ea2f5b76308b.png)



## コード実装
### 実装前の準備
```bash
mkdir demo
cd demo
mkdir main.go
mkdir templates
touch templates/index.html
touch .env

code .
```

### main.goの実装
```main.go
package main

import (
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"

	"github.com/gin-gonic/gin"
	"github.com/joho/godotenv"
	"github.com/markbates/goth"
	"github.com/markbates/goth/gothic"
	"github.com/markbates/goth/providers/google"
)

func main() {
	// Ginルーターの初期化
	r := gin.Default()

	// .envファイルの読み込み（環境変数の設定）
	err := godotenv.Load()
	if err != nil {
		log.Fatal(".envファイルの読み込みに失敗しました!")
	}

	// 環境変数からOAuth認証に必要な情報を取得
	clientID := os.Getenv("CLIENT_ID")
	clientSecret := os.Getenv("CLIENT_SECRET")
	clientCallbackURL := os.Getenv("CLIENT_CALLBACK_URL")

	// 必要な環境変数が設定されているか確認
	if clientID == "" || clientSecret == "" || clientCallbackURL == "" {
		log.Fatal("環境変数（CLIENT_ID、CLIENT_SECRET、CLIENT_CALLBACK_URL）が必要です")
	}

	// Googleプロバイダを使用したOAuth認証の設定
	// スコープを明示的に指定：ユーザー情報とメールアクセス
	googleProvider := google.New(
		clientID,
		clientSecret,
		clientCallbackURL,
		"https://www.googleapis.com/auth/userinfo.email",
		"https://www.googleapis.com/auth/userinfo.profile",
	)

	goth.UseProviders(googleProvider)

	// HTMLテンプレートの読み込み
	r.LoadHTMLGlob("templates/*")

	// ルーティングの設定
	r.GET("/", home)                                   // トップページ
	r.GET("/auth/:provider", signInWithProvider)       // 認証開始エンドポイント
	r.GET("/auth/:provider/callback", callbackHandler) // コールバックエンドポイント
	r.GET("/success", Success)                         // 認証成功後のページ

	// サーバーの起動（8080ポート）
	r.Run(":8080")
}

func home(c *gin.Context) {
	// インデックスページのテンプレート読み込み
	tmpl, err := template.ParseFiles("templates/index.html")
	if err != nil {
		// テンプレート読み込みエラー時は500内部サーバーエラーを返す
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}

	// テンプレートの実行
	err = tmpl.Execute(c.Writer, gin.H{})
	if err != nil {
		// テンプレート実行エラー時は500内部サーバーエラーを返す
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}
}

func signInWithProvider(c *gin.Context) {
	// 認証プロバイダの取得（この場合はGoogle）
	provider := c.Param("provider")
	q := c.Request.URL.Query()
	q.Add("provider", provider)
	c.Request.URL.RawQuery = q.Encode()

	// 認証プロセスの開始
	gothic.BeginAuthHandler(c.Writer, c.Request)
}

func callbackHandler(c *gin.Context) {
	// 認証プロバイダの取得
	provider := c.Param("provider")
	q := c.Request.URL.Query()
	q.Add("provider", provider)
	c.Request.URL.RawQuery = q.Encode()

	// ユーザー認証の完了
	user, err := gothic.CompleteUserAuth(c.Writer, c.Request)
	if err != nil {
		// 認証エラー時は500内部サーバーエラーを返す
		c.AbortWithError(http.StatusInternalServerError, err)
		return
	}

	// ユーザー情報をログに出力
	log.Printf("ログインユーザー情報:")
	log.Printf("プロバイダ: %s", user.Provider)
	log.Printf("ユーザーID: %s", user.UserID)
	log.Printf("ニックネーム: %s", user.NickName)
	log.Printf("メールアドレス: %s", user.Email)
	log.Printf("名前: %s", user.Name)
	log.Printf("画像URL: %s", user.AvatarURL)

	// 認証成功後、successページにリダイレクト
	c.Redirect(http.StatusTemporaryRedirect, "/success")
}

func Success(c *gin.Context) {
	// 認証成功後に表示するシンプルなHTMLページ
	c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(fmt.Sprintf(`
      <div style="
          background-color: #fff;
          padding: 40px;
          border-radius: 8px;
          box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
          text-align: center;
      ">
          <h1 style="
              color: #333;
              margin-bottom: 20px;
          ">ログインに成功しました！</h1>
          
          </div>
      </div>
  `)))
}
```
今回の実装では`goth`と`gothic`ライブラリを使用して、Googleのログイン機能を簡単に実装しています。
主に3つの重要な関数で構成されています：

#### 1. ログイン開始 (`signInWithProvider`)
ユーザーがGoogleログインボタンをクリックすると、Googleの認証ページにリダイレクトされます
`gothic.BeginAuthHandler()`が認証プロセスを開始させる関数です

#### 2. コールバック処理 (`callbackHandler`)

Googleからユーザー情報を受け取る
ログイン成功後のユーザー情報を取得
ユーザーのメールアドレス、名前、プロフィール画像などにアクセス可能

:::note info
ここでセッションの追加やDBへの保存の関数の呼び出しを行う
:::



#### 3. 成功ページ (`Success`)

ログイン成功後に表示されるシンプルな画面

:::note info
#### スコープの設定
```go
googleProvider := google.New(
    clientID,
    clientSecret,
    clientCallbackURL,
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/userinfo.profile",
)
```
userinfo.email: ユーザーのメールアドレスにアクセス
userinfo.profile: ユーザーの基本プロファイル情報にアクセス

他の一般的なGoogleスコープ例
- https://www.googleapis.com/auth/drive: Googleドライブへのアクセス
- https://www.googleapis.com/auth/calendar: カレンダーへのアクセス
- https://www.googleapis.com/auth/contacts: 連絡先へのアクセス

以下に、他のスコープに関するリファレンスがありますので以下から必要なスコープを探して追加してください。
https://developers.google.com/identity/protocols/oauth2/scopes?hl=ja
:::

:::note warn
### スコープ選択
**スコープはとても便利ですが以下の３点を心がけて使うようにしましょう**
- 最小権限の原則に従い、必要最小限のスコープを要求
- ユーザーのプライバシーを尊重
- アプリケーションの目的に必要な情報のみをリクエスト
:::


### ログイン前のホーム画面の作成
```templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Sign in with Google</title>
</head>
<body 
     style="
         font-family: Arial, sans-serif; 
         background-color: #f0f0f0; 
         display: flex; 
         justify-content: center; 
         align-items: center; 
         height: 100vh;"
>
  <div 
     style="
        background-color: #fff; 
        padding: 40px; 
        border-radius: 8px; 
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 
        text-align: center;"
    >
        <h1 style="color: #333; margin-bottom: 20px;">Welcome</h1>
        <a href="/auth/google"  style="
        display: inline-flex; 
        align-items: center; 
        justify-content: center; 
        background-color: #4285f4; 
        color: #fff; 
        text-decoration: none; 
        padding: 12px 20px; 
        border-radius: 4px; 
        transition: background-color 0.3s ease;">
        <span style="font-size: 16px; font-weight: bold;">Sign in with Google</span>
        </a>
  </div>
</body>
</html>
```

### 環境変数の設定
```.env
CLIENT_ID=clientID
CLIENT_SECRET=clientSecret
CLIENT_CALLBACK_URL=http://localhost:8080/auth/google/callback
```
環境変数として、以下のものを定義しています
- `CLIENT_ID`: Googleから取得したクライアントID
- `CLIENT_SECRET`: クライアントシークレット
- `CLIENT_CALLBACK_URL`: 認証後のリダイレクトURL

### プロジェクトの依存関係 初期化
```bash
go mod init demo
go mod tidy
```

# 実装確認

以下のコマンドで実装したコードを試してみましょう
```bash
go run main.go
```

エラーなく実行できていたら以下のURLにアクセスしてみてください
```
http://localhost:8080/
```

アクセスするとこのようにログインのためのボタンだけのページが表示されます。
![スクリーンショット 2024-12-15 22.17.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759397/1f7147bd-f12f-c61b-d8ef-7dc62218f3df.png)

ログインが成功すると以下のような画面になります。
また、実行しているGoのターミナルで実際にログインしたユーザーの情報を確認できます。
![スクリーンショット 2024-12-15 22.16.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759397/ed274718-8e75-a833-d465-39e2d053dfdb.png)

# 参考

https://developers.google.com/identity/protocols/oauth2

https://github.com/golang/oauth2