🦔

【備忘録】GoogleのOAuth2.0でリフレッシュトークンが返ってこない件

2025/02/18に公開

前提

GoogleOAuth2.0をある程度知っている

環境

フロント Next.js
バックエンド Go(gin)
OAuth2.0ライブラリ

	"golang.org/x/oauth2"
	googleOAuth "golang.org/x/oauth2/google"
	v2 "google.golang.org/api/oauth2/v2"

結論

フロントからGoogleの認証画面にリダイレクトする際に叩くURLのパラメータを確認する。
access_type=offlineapproval_prompt=forceをクエリパラメータにつける。

https://accounts.google.com/o/oauth2/auth
?client_id=hogehogehoge
&redirect_uri=http://localhost:3000/login
&response_type=code
&scope=openid email profile
&access_type=offline
&approval_prompt=force

どういうことか

最後のaccess_type=offlineapproval_prompt=forceがついていないと、Googleの同意画面が簡略化されたものになる。
→ 簡略化というと語弊があるが、ユーザー目線でいうと、アカウント選ぶだけ終わる。
しかし、これらのパラメータがつくと、アカウントを選んだ後にさらに同意画面が出てくる。
→ ユーザー目線では『このアプリを信用しますか?』はいorいいえ 的な選択が挟まる。

いずれの場合も完了後、自分のアプリへのリダイレクト時にcodeは発行される。
しかし、その後のフローとして自身のバックエンドでcodeを使って、認証を行った時のOAuthのレスポンスが異なる。
先述のGoogleの画面でユーザーが同意画面で同意した場合にのみ、リフレッシュトークンを得ることができる。

func (g *GoogleOAuthManager) GetAccessToken(code string) (oAuthInfo *OAuthInfo, err error) {
	utils.LogMessage(utils.Debug, "GetAccessToken")
	cxt := context.Background()

	tokenInfo, _ := g.Config.Exchange(cxt, code)
	if tokenInfo == nil {
		utils.LogMessage(utils.Error, "failed get google token")
		return nil, errors.New("failed get google token")
	}
println(tokenInfo.RefreshToken) // ここが空かどうかが変わる

使い道(ただ当たり前のこと書いてます。。。)

バックエンドでユーザーのトークンの期限の管理や認証情報のリフレッシュの制御に合わせて、
細かいカスタマイズが可能。

ユーザー登録の際には同意画面を表示させ、リフレッシュトークンを発行する。
それ以降は、ログイン画面として簡略バージョンの認証画面にしておく。
毎回同意画面を表示させることなく、ボタン一発のスムーズなログインを実現することでユーザビリティに貢献できる。

基本的にアクセストークンはリフレッシュトークンやログインのたびに頻繁に更新するが、
リフレッシュトークンは頻繁に更新するようなものではない。
しかし、セキュリティ上の理由で、ユーザーのリフレッシュトークンごと更新したいケースがあるかもしれない。
バックエンド側で判定ロジックを組めば、意図的にユーザーに同意を求めることができる。

つぶやき

久々に触ったら30分ほど溶かしたので、備忘録を残しておきました。
誰かの役に立てばと。。。
(ネットでいっぱい出てくるけど。)

approval_prompt=forceは古いという情報があるが、私の環境だとこれで動くんだよね。。。なんだろうか。

Discussion