GoogleでのOAuth2ログインを用いるためにこれまで使用していたJavaScriptプラットフォームライブラリが2023年3月31日に廃止される。そのためこの機能を利用しているなら、Google Identity Servicesに移行する必要がある。既存のクライアントIDは影響を受けないが、新しく作成されたクライアントIDは古いプラットフォームライブラリを使用できない1。また2022年07月29日以前に作成された新しいクライアントIDについては plugin_name
を指定することで、Googleプラットフォームライブラリを有効化できる。今回はこのGoogle Identity Servicesを使用しGoogleのOAuth2ログインを利用する方法を試す。
シンプルな方法
まずはGoogleのドキュメントに直接記載のある最もシンプルな方法から試す。つまりフロントエンドフレームワークなどを用いず、HTML(とVanilla JS)で記述する。
UXモード
Google Identity Servicesでの認証の処理時にどのような画面遷移を行うかというUX観点での方式としてポップアップ方式とリダイレクト方式がある。
ポップアップ方式
ポップアップ方式ではボタンを押すと、Googleの認証画面が表示される。認証に必要な情報を入力すると、元の画面に戻り指定したコールバック関数が呼び出される。
<!DOCTYPE html>
<html>
<head>
<script src="https://accounts.google.com/gsi/client" async defer></script>
<script>
function handleCredentialResponse (arg) {
console.log(arg);
alert("Ok");
}
</script>
</head>
<body>
<div id="g_id_onload"
data-client_id="YOUR-GOOGLE-CLIENT-ID"
data-callback="handleCredentialResponse">
</div>
<div class="g_id_signin" data-type="standard"></div>
</body>
</html>
コールバック関数の呼び出し時の引数としてGoogleから取得した認証情報が渡される。この引数は以下のような形式となっている。
{ "clientId": "CLIENT_ID", "credential": "CREDENTIAL", "select_by": "btn" }
リダイレクト方式
リダイレクト方式ではボタンを押すと、Googleの認証画面が表示される。認証に必要な情報を入力すると、元の画面に戻り指定したURLに対しフォームをPOSTで送信する。
<!DOCTYPE html>
<html>
<body>
<script src="https://accounts.google.com/gsi/client" async defer></script>
<div id="g_id_onload"
data-client_id="YORU-GOOGLE-CLIENT-ID"
data-ux_mode="redirect"
data-login_uri="YOUR-REDIRECT-URL">
</div>
<div class="g_id_signin" data-type="standard"></div>
</body>
</html>
フォームの値は次のような形式となっている。
clientId=YOUR_CLIENT_ID&credential=CREDENTIAL&select_by=btn&g_csrf_token=CSRF_TOKEN
認可フローとして認可コードフローを使う
OAuth2.0には複数の認可フローが定義されている。上記で実装したものはインプリシットフローというものであるが、通常のサービスではセキュリティ上の理由から使用するべきではない。
そこでここでは認可コードフローを試すことにする2。
しかし Google Identity Services
を用いた時にそれぞれの認可フローを使用するにはどうすればよのか、ドキュメント3を読んだだけではよくわからなかった。実装して挙動を確認することにする。認可エンドポイントに対してリクエストを送信し、そのレスポンスとして認可コードと呼ばれる値を取得する。そして認可コードを用いてアクセストークンを取得する。認可コードフローを用いるためには google.accounts.oauth2.initCodeClient
を用いてクライアントを初期化する。
<!DOCTYPE html>
<html>
<head>
<script src="https://accounts.google.com/gsi/client" onload="initClient()" async defer></script>
<script>
var client;
var clientConfig = {
client_id: 'YOUR-GOOGLE-CLIENT-ID',
scope: 'https://www.googleapis.com/auth/calendar.readonly',
ux_mode: 'popup',
}
function postAuthCode (response) {
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:8080/authcode", true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('code=' + response.code)
}
function initClient() {
client = google.accounts.oauth2.initCodeClient({
callback: postAuthCode,
...clientConfig
});
}
</script>
</head>
<body>
<button onclick="client.requestCode();">Load Your Calendar</button>
</body>
</html>
@react-oauth/google - React用Google Auth実装
Reactでこの機能を使用したければ自分で上記の挙動を実装することで実現できる。ただし通常はReact用のライブラリを探し、もしそれが存在していて使用できそうであれば、それを使用することも選択肢となる。ここではReact用のライブラリとして実装されている@react-oauth/google4を使用してGoogle認証を実装する。 @react-oauth/google
は GoogleOAuthProvider
と GoogleLogin
コンポーネントを提供している。GoogleOAuthProviderはReactのコンテキストプロバイダーだ。コンテキストプロバイダーはそのタグで囲まれたどの子孫要素からでも、プロバイダーが提供する変数にアクセスできる。 GoogleOAuthProvider
にクライアントIDを渡すことで子孫要素からその値にアクセスできる。
import logo from './logo.svg';
import './App.css';
import { GoogleOAuthProvider, GoogleLogin } from '@react-oauth/google';
import GoogleAuthInplicitFlow from './components/GoogleAuthInplicitFlow';
import GoogleAuthAuthCodeFlow from './components/GoogleAuthAuthCodeFlow';
function App() {
return (
<div className="App">
<GoogleOAuthProvider clientId="YOUR-GOOGLE-CLIENT-ID">
<GoogleAuthInplicitFlow></GoogleAuthInplicitFlow>
<GoogleAuthAuthCodeFlow></GoogleAuthAuthCodeFlow>
</GoogleOAuthProvider>
</div>
);
}
export default App;
そして GoogleLogin
はその提供されたクライアントIDを用いて認証処理を行う。デフォルトではインプリシットフローが選択される5。 GoogleLogin
コンポーネントはボタンの表示も担うため、任意の場所に設置したいだろう。App.jsなどかなり上位の箇所に GoogleOAuthProvider
を設置しておけば、その配下の任意の場所に GoogleLogin
は設置できる。
import { GoogleLogin } from '@react-oauth/google';
function GoogleAuthInplicitFlow() {
return (
<div>
<GoogleLogin
onSuccess={credentialResponse => {
console.log(credentialResponse);
}}
onError={() => {
console.log('Login Failed');
}}
/>
</div>
);
}
export default GoogleAuthInplicitFlow;
認証に成功した場合はonSuccessが呼び出される。呼び出し時の引数として認証情報が渡される。この認証情報の形式は「シンプルな方法のポップアップ方式」で説明したものと同じものだ。認証に失敗した場合はonErrorが呼び出される。もし認証コードフローを用いるのであれば useGoogleLogin
フックを用いて次のようにする必要がある。
import { GoogleOAuthProvider, useGoogleLogin, GoogleLogin } from '@react-oauth/google';
function GoogleAuthAuthCodeFlow() {
const login = useGoogleLogin({
flow: 'auth-code',
onSuccess: codeResponse => {
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:8080/authcode", true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('code=' + codeResponse.code)
},
});
return (
<div>
<button onClick={() => login()}>Login with Google</button>
</div>
);
}
export default GoogleAuthAuthCodeFlow;
簡易サーバの実装
今回はサーバサイドについては割愛した。仮の実装として適宜データを受けとるために、mitmproxyのスクリプトとして以下を使用した。
from mitmproxy import http
def request(flow: http.HTTPFlow):
if flow.request.path == "/authcode":
flow.response = http.Response.make(
200, content=b"Ok"
)
return
処理自体を省略しており、仮のレスポンスを返すだけだ。必要に応じてサーバーサイドの実装は実施してほしい。
まとめ
Goolge JavaScriptプラットフォームライブラリが廃止になるため、Google Identity Servicesを用いたGoogleのOAuth2ログインの実装方法と挙動を確認した。Googleのドキュメントに記載のあるHTML(とVanilla JS)で記述するシンプルな方法と、React用の実装である@react-oauth/googleを用いる方法を試した。
脚注
古いコードで新しいIDを使用すると、認証後に画面が突如閉じるという挙動をしていた。この原因がしばらくわからずハマった。
コードを確認したところどうやらデフォルトではインプリシットフローで実行されるようだ。インプリシットフローは脆弱なフローであるため実際のサービスでは使用しないほうがよい。https://github.com/MomenSherif/react-oauth/blob/787ed022133546619765ad25261598347fe98948/packages/%40react-oauth/google/src/hooks/useGoogleLogin.ts#L67