GolangでOIDCを使った組織内認証を実験してみた!
GoとOpenID Connectを使用したOAuth 2.0の実装
この記事では、Googleのアイデンティティサービスを使用して認証を行うOAuth 2.0およびOpenID Connectを利用したGoアプリケーションについて詳しく説明します。コードを一つずつ分解して、その動作を理解します。
自己紹介
とある長期インターンでSaaSの作成を行っています!
残念ながらひとりで作成中です...
経験もSQLとGAS, Pythonのスクレイピングのみ...
とほほ...
概要
このコードは、GoogleのOAuth 2.0およびOpenID Connectを介してユーザーを認証する基本的なWebサーバーをGoで設定する方法を示しています。これを実現するために、go-oidc
、godotenv
、oauth2
などのパッケージを使用しています。
前提条件
- Goのプログラミング知識
- OAuth 2.0とOpenID Connectの基本的な理解
- OAuth 2.0の資格情報を持つGoogle Cloudプロジェクト
ソースコード
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"github.com/coreos/go-oidc"
"github.com/joho/godotenv"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
var (
// 環境変数の定義
clientID string
clientSecret string
// Google API Consoleに設定したリダイレクトURL
redirectURL = "http://localhost:8080/callback"
provider *oidc.Provider // OIDCプロバイダー
config *oauth2.Config // OAuth 2.0設定
)
func init() {
// .envファイルを読み込む
if err := godotenv.Load(); err != nil {
log.Fatalf("Error loading .env file: %v", err)
}
// 環境変数からクライアントIDとクライアントシークレットを取得
clientID = os.Getenv("CLIENT_ID")
clientSecret = os.Getenv("CLIENT_SECRET")
// OIDCプロバイダーを初期化(Googleを指定)
var err error
provider, err = oidc.NewProvider(context.Background(), "https://accounts.google.com")
if err != nil {
log.Fatalf("failed to get provider: %v", err)
}
// OAuth 2.0設定を初期化
config = &oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: redirectURL,
Endpoint: google.Endpoint, // GoogleのOAuth 2.0エンドポイント
Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, // 要求するスコープ
}
}
func main() {
// ハンドラ関数をルートにマッピング
http.HandleFunc("/", handleMain)
http.HandleFunc("/login", handleLogin)
http.HandleFunc("/callback", handleCallback)
// サーバーを起動
fmt.Println("Server started at http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
// メインページのハンドラ
func handleMain(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello! Please <a href='/login'>log in</a>")
}
// ログイン処理のハンドラ
func handleLogin(w http.ResponseWriter, r *http.Request) {
// 認証URLを生成し、そこへリダイレクト
url := config.AuthCodeURL("state", oauth2.AccessTypeOffline)
http.Redirect(w, r, url, http.StatusFound)
}
// OAuthコールバック処理のハンドラ
func handleCallback(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
// 認証コードを使ってトークンを交換
oauth2Token, err := config.Exchange(ctx, r.URL.Query().Get("code"))
if err != nil {
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
return
}
// トークンからIDトークンを取得
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
return
}
// IDトークンを検証
idToken, err := provider.Verifier(&oidc.Config{ClientID: clientID}).Verify(ctx, rawIDToken)
if err != nil {
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
return
}
// IDトークンからユーザー情報を取得
var profile map[string]interface{}
if err := idToken.Claims(&profile); err != nil {
http.Error(w, "Failed to get user profile: "+err.Error(), http.StatusInternalServerError)
return
}
// ユーザーのドメインが許可されているかを検証
if domain, ok := profile["hd"].(string); !ok || domain != "dev-ryo.com" {
http.Error(w, "Unauthorized domain", http.StatusUnauthorized)
return
}
// ログイン成功メッセージを表示
fmt.Fprintf(w, "Login successful: %+v", profile)
}
依存関係のインポート
import (
"context"
"fmt"
"log"
"net/http"
"os"
"github.com/coreos/go-oidc"
"github.com/joho/godotenv"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
コードは必要なパッケージをインポートすることから始まります:
- HTTPサーバー機能のための
context
とhttp
- ログ記録のための
fmt
とlog
- 環境変数アクセスのための
os
- OAuthおよびOpenID Connectプロセスのための
go-oidc
、godotenv
、oauth2
グローバル変数
var (
clientID string
clientSecret string
redirectURL = "http://localhost:8080/callback"
provider *oidc.Provider
config *oauth2.Config
)
ここでは、クライアントID、クライアントシークレット、リダイレクトURL、OIDCプロバイダー、OAuth 2.0の設定を含むOAuth設定のためのグローバル変数を定義します。
初期化
func init() {
if err := godotenv.Load(); err != nil {
log.Fatalf("Error loading .env file: %v", err)
}
clientID = os.Getenv("CLIENT_ID")
clientSecret = os.Getenv("CLIENT_SECRET")
var err error
provider, err = oidc.NewProvider(context.Background(), "https://accounts.google.com")
if err != nil {
log.Fatalf("failed to get provider: %v", err)
}
config = &oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: redirectURL,
Endpoint: google.Endpoint,
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
}
init
関数は環境変数をロードし、OIDCプロバイダーを初期化し、OAuth 2.0の設定を行います。
サーバーのセットアップとハンドラ
func main() {
http.HandleFunc("/", handleMain)
http.HandleFunc("/login", handleLogin)
http.HandleFunc("/callback", handleCallback)
fmt.Println("Server started at http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
main
では、HTTPルートを定義し、サーバーを開始します。各ルートはOAuthフローの異なる部分を処理するハンドラ関数にマップされています。
ハンドラ関数
-
handleMain
はログインリンクを表示します。 -
handleLogin
はユーザーをGoogleのOAuth 2.0ログイン画面にリダイレクトします。 -
handleCallback
はOAuthコ
ールバックを処理し、認証コードをトークンと交換し、ユーザーのプロファイルを取得します。
GoにおけるOAuth 2.0のハンドラー関数に深く潜る
この記事では、Goアプリケーションで使用されるハンドラー関数を探り、OpenID Connectを用いたOAuth 2.0フローを管理する方法を解説します。各関数は認証プロセスにおいて重要な役割を果たし、ユーザーをログインへ導き、結果のデータを処理します。
ハンドラーの理解
アプリケーションは三つの主要なハンドラー関数を定義しています: handleMain
、handleLogin
、handleCallback
。これらの関数はユーザーのブラウザとOAuth 2.0プロバイダーと対話し、認証を容易にします。
メインハンドラー: handleMain
func handleMain(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello! Please <a href='/login'>log in</a>")
}
目的: アプリケーションの入り口として機能し、ユーザーにログインリンクを提示します。
プロセス:
/loginルートへのリンクを含むシンプルなHTMLの挨拶を出力し、ログインプロセスを開始します。
ログインハンドラー: handleLogin
func handleLogin(w http.ResponseWriter, r *http.Request) {
url := config.AuthCodeURL("state", oauth2.AccessTypeOffline)
http.Redirect(w, r, url, http.StatusFound)
}
目的: ユーザーをプロバイダーのログインページにリダイレクトして、OAuth 2.0フローを開始します。
プロセス:
OAuth 2.0設定からAuthCodeURLを使用して認証URLを生成し、CSRF保護のためのstateパラメーターを含めます。
ユーザーを認証URLにリダイレクトし、そこでユーザーは自分の認証情報を使用してログインし、許可を与えます。
コールバックハンドラー: handleCallback
func handleCallback(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
oauth2Token, err := config.Exchange(ctx, r.URL.Query().Get("code"))
if err != nil {
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
return
}
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
return
}
idToken, err := provider.Verifier(&oidc.Config{ClientID: clientID}).Verify(ctx, rawIDToken)
if err != nil {
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
return
}
var profile map[string]interface{}
if err := idToken.Claims(&profile); err != nil {
http.Error(w, "Failed to get user profile: "+err.Error(), http.StatusInternalServerError)
return
}
if domain, ok := profile["hd"].(string); !ok || domain != "dev-ryo.com" {
http.Error(w, "Unauthorized domain", http.StatusUnauthorized)
return
}
fmt.Fprintf(w, "Login successful: %+v", profile)
}
目的: ユーザーがログインしてアプリケーションを認証した後、OAuth 2.0プロバイダーからのコールバックを処理します。
プロセス:
コールバックリクエストから認証コードを抽出し、config.Exchange
を使用してアクセストークンとIDトークンと交換します。
期待される発行者からのIDトークンを確認し、このアプリケーション向けで
あることを検証します。
必要に応じてドメインを検証しながら、ユーザーのプロファイル情報を抽出して表示します。
ハンドラー関数のまとめ
このGoアプリケーションのハンドラー関数は、ユーザーエクスペリエンスにOAuth 2.0フローをシームレスに統合します。これらはOAuth 2.0プロバイダー(この場合はGoogle)と対話し、ユーザーを認証し、アクセストークンを取得し、ユーザープロファイル情報を取得します。このプロセスはWebアプリケーションにおける典型的なOAuth 2.0フローを示し、Goを使用して安全かつ効率的な認証メカニズムを実装する方法を強調しています。
結論
このGoアプリケーションは、ユーザー認証のためのGoogleのOAuth 2.0およびOpenID Connectとの統合の基本的な例を提供します。Webサーバーを設定し、OAuthを構成し、認証フローを処理し、Goでこれらのプロトコルの実用的な実装を示しています。
アプリケーションでOAuth 2.0とOpenID Connectを理解し、実装することは、認証のための確立されたプロトコルとサービスを利用することで、セキュリティとユーザー体験を大幅に向上させることができます。
Discussion
echoフレームワークを使用したものに書き換えました!