自作のCLIツールで「Googleでログイン」機能を実装したい場合、Webサービスとは異なる**「パブリッククライアント」**特有の設計が必要になります。本記事では、その標準的な仕組みである「Authorization Code Flow with PKCE」と、Go言語の定番フレームワーク Cobra を使った実装方法を解説します。
CLIにはブラウザがないため、ログインを完結させるために一時的なローカルWebサーバーを立ち上げる手法が一般的です。
認可URLの生成: CLIがGoogleの認可エンドポイントURLを作成します。
待機: CLI内部で一時的なHTTPサーバー(例: localhost:8080)を起動します。
ブラウザ起動: ユーザーの標準ブラウザで認可URLを開きます。
ユーザー認証: ユーザーがブラウザ上でGoogleログインを承認します。
リダイレクト: Googleが認可コードを http://localhost:8080?code=... に送信します。
トークン交換: CLIがそのコードをキャッチし、バックエンドでGoogleと通信して「アクセストークン」を取得します。
Bash
go get github.com/spf13/cobra
go get golang.org/x/oauth2
go get golang.org/x/oauth2/google
cmd/login.go)以下は、login コマンドを実行するとブラウザが開き、認証後にトークンを表示する最小構成のコードです。
Go
package cmd
import (
"context"
"fmt"
"net/http"
"os/exec"
"runtime"
"github.com/spf13/cobra"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
var loginCmd = &cobra.Command{
Use: "login",
Short: "Googleアカウントでログイン",
Run: func(cmd *cobra.Command, args []string) {
handleLogin()
},
}
func handleLogin() {
ctx := context.Background()
// 1. OAuth2 設定
// ClientID/Secret は Google Cloud Console で「デスクトップアプリ」として作成したもの
conf := &oauth2.Config{
ClientID: "YOUR_CLIENT_ID.apps.googleusercontent.com",
ClientSecret: "YOUR_CLIENT_SECRET",
Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"},
Endpoint: google.Endpoint,
RedirectURL: "http://localhost:18080",
}
// 2. 認可コード受け取り用のチャネル
codeChan := make(chan string)
// 3. ローカルサーバーの起動
server := &http.Server{Addr: ":18080"}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
if code != "" {
fmt.Fprintf(w, "ログインが完了しました。ターミナルに戻ってください。")
codeChan <- code // コードをメイン処理に送る
}
})
go func() {
if err := server.ListenAndServe(); err != http.ErrServerClosed {
fmt.Printf("Server error: %v\n", err)
}
}()
// 4. ブラウザで認証ページを開く
authURL := conf.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
fmt.Printf("ブラウザを開いています...\n%s\n", authURL)
openBrowser(authURL)
// 5. コード取得までブロック
code := <-codeChan
// 6. トークン交換
tok, err := conf.Exchange(ctx, code)
if err != nil {
fmt.Printf("トークン交換失敗: %v\n", err)
return
}
fmt.Println("\nログイン成功!")
fmt.Printf("Access Token: %s\n", tok.AccessToken)
// サーバーをクリーンアップ
server.Shutdown(ctx)
}
// OSごとにブラウザを開く補助関数
func openBrowser(url string) {
var err error
switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
err = exec.Command("open", url).Start()
}
if err != nil {
fmt.Printf("ブラウザを自動で開けませんでした: %v\n", err)
}
}
CLI(パブリッククライアント)はバイナリを解析されると Client Secret が漏洩するリスクがあります。そのため、現在は PKCE (Proof Key for Code Exchange) を併用するのがベストプラクティスです。golang.org/x/oauth2 を使う場合、S256 チャレンジを生成して AuthCodeURL のオプションに渡すことで実装可能です。
取得した tok.AccessToken や tok.RefreshToken は、平文のファイルではなく、OS標準のセキュアなストレージに保存することを検討してください。
macOS: Keychain
Linux: Secret Service / libsecret
Windows: Credential Locker
Goでは keyring ライブラリなどを使うと簡単に扱えます。
CLIでのGoogleログインは、「ローカルWebサーバーを立ててリダイレクトを待ち受ける」という少しトリッキーな動きをします。しかし、この流れを理解すれば、Google以外の多くのSaaS(GitHubやAWSなど)のCLIログインも同じ仕組みで実装できることがわかるはずです。