Next.js + GoでGoogle SSOを実現する
Next.js + Go でGoogle SSOを実現する(Google編)
1年以上前
Google公式を参考に設定していく(少しわかりづらい)
以下ポイントだけ記載
- 認証情報で認証情報を作成 -> OAuth 2.0 クライアント IDを作成する
- OAuth 2.0 クライアント IDの作成で、承認済みのリダイレクト URIにバックエンドで実装したコールバックAPIを設定する
- 本番運用する場合、OAuth 同意画面にて、ロゴとホームページ・プライバシーポリシー・利用規約のURLが必要になるため事前に準備する
- リジェクトされた場合はメールに理由が届き、対応したらメールに返信する必要がある
Next.js + Go でGoogle SSOを実現する(バックエンド編)
1年以上前
コールバック用のAPIを準備
func (h *GoogleAuthHandler) HandleGoogleCallback(c *gin.Context) {
code := c.Query("code")
err := c.Query("error")
ctx := c.Request.Context()
if err != "" {
c.Redirect(http.StatusFound, os.Getenv("APP_URL")+"/login")
return
}
googleUser, authError := h.GoogleAuthService.GetGoogleAuthUser(code)
if authError != nil {
c.Redirect(http.StatusFound, os.Getenv("APP_URL")+"/login")
return
}
// 後継処理を書く
}コールバックで受け取ったcodeをdecodeし、accessTokenを使ってOpenID Connectへリクエストする 受け取ったresponseからuser情報を取得
package google
import (
"context"
"encoding/json"
"net/http"
"github.com/mizuko-dev/paput-api/config"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
type GoogleAuthUser struct {
Sub string `json:"sub"`
Email string `json:"email"`
VerifiedEmail bool `json:"verified_email"`
Name string `json:"name"`
GivenName string `json:"given_name"`
FamilyName string `json:"family_name"`
Picture string `json:"picture"`
Locale string `json:"locale"`
}
type GoogleAuthService struct {
OAuthConfig *oauth2.Config
}
func NewGoogleAuthService() GoogleAuthService {
config, _ := config.LoadConfig()
oauthConfig := &oauth2.Config{
RedirectURL: config.Google.RedirectURL,
ClientID: config.Google.ClientID,
ClientSecret: config.Google.ClientSecret,
Scopes: []string{
"email",
"profile",
},
Endpoint: google.Endpoint,
}
return GoogleAuthService{
OAuthConfig: oauthConfig,
}
}
func (r *GoogleAuthService) GetGoogleAuthUser(code string) (*GoogleAuthUser, error) {
token, err := r.OAuthConfig.Exchange(context.Background(), code)
if err != nil {
return nil, err
}
client := &http.Client{}
req, _ := http.NewRequest("GET", "https://openidconnect.googleapis.com/v1/userinfo", nil)
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
response, err := client.Do(req)
if err != nil {
return nil, err
}
defer response.Body.Close()
var GoogleAuthUser GoogleAuthUser
if err := json.NewDecoder(response.Body).Decode(&GoogleAuthUser); err != nil {
return nil, err
}
return &GoogleAuthUser, nil
}Next.js + Go でGoogle SSOを実現する(フロントエンド編)
1年以上前
ボタン押下時のhandler
'use client';
export const handleGoogleSignIn = (): void => {
const clientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID;
const redirectUri = process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI;
// Google OAuth 2.0 URLの構築
const scope = 'email profile openid';
const responseType = 'code';
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=${responseType}&access_type=offline&prompt=consent`;
// ユーザーをGoogleの認証ページへリダイレクト
window.location.href = authUrl;
};ボタンのデザイン(ボタンはGoogleのデザイン規約に準拠)
<GoogleSingInButton handler={handleGoogleSignIn} />ボタンを押下すると、Google側のログイン画面へ遷移する
フロント起点のOAuthログインにstate(CSRF)を効かせるにはバックエンド開始エンドポイントが必要
約2か月前
OAuthのstate(CSRF対策)は『stateを発行する側』と『検証する側』が同一でないと成立しない。SPA等でフロントがGoogle認可URLを自前で組み立て、コールバックだけがバックエンドにある構成では、バックエンドのコールバックだけでstate検証を後付けしても保護できない(フロントが任意stateを送れる/Cookieが無い)。
対策パターン: バックエンドにログイン開始エンドポイント(例 GET /auth/google/login)を新設し、(1)crypto/randでstate生成 → (2)HttpOnlyかつ短寿命のstate Cookieに保存 → (3)同じstateを埋め込んだ認可URLへ302リダイレクト。コールバックでクスエリのstateとCookieのstateを定数時間比較(crypto/subtle.ConstantTimeCompare)で突合し、検証可否に関わらずstate Cookieは使い捨て破棄する。フロントは認可URLを自前生成せず、この開始エンドポイントへフルページ遷移させる(XHR不可。Set-Cookie+リダイレクト連鎖が必要なため)。
Google OIDC userinfoのメール確認フラグはemail_verified(旧v2はverified_email)
約2か月前
GoogleのOpenID Connect userinfoエンドポイント(https://openidconnect.googleapis.com/v1/userinfo)が返すメール確認済みフラグは『email_verified』。一方、旧来の https://www.googleapis.com/oauth2/v2/userinfo は『verified_email』を返す。エンドポイントを移行/混在させるとJSONタグ名の不一致でフラグが常にfalse(ゼロ値)になり、(a)未確認メールチェックが機能しない or (b)逆に全ログインを誤って拒否する、といったバグになる。
セキュリティ上、email/sub等でユーザーを突合してログインさせる実装では email_verified==true を必須チェックすること。未確認メールを許すと、攻撃者が被害者のメールアドレスで未確認アカウントを作りログインできるなりすまし(アカウント乗っ取り)に繋がりうる。