Unityエディタからライブラリを使わずにGoogle APIのOAuth2認証をする方法をまとめました。
Unity2019.3.5
はじめに
この記事ではUnityエディタからGoogle APIのOAuth2認証をする方法をまとめます。
今回はGoogle公式クライアントライブラリを使わずにAPIを叩いて実装してみます。
また本記事における例として、Spreadsheetからデータを読み込むことを目的とします。
他の用途に使う際にはSpreadsheetの部分を適宜読み替えてください。
ちなみにOAuth認証無しでSpreadSheetを読む方法は以下で紹介しています。
また、クライアントライブラリを使った方法は以下の記事にまとめています。
Google APIの初期設定を行う
OAuth認証を使うには、まず認証情報を取り扱うアプリケーションの情報を登録する必要があります。
登録を行うためにGoogleのDeveloper Consoleにアクセスします。
すると以下のようなページが表示されます。
(スクリーンショットは英語表記になっていますが適宜読み替えてください)
プロジェクトを作成
まず左上のSelect a project > NEW PROJECT
を選択して新しくプロジェクトを作成します。
以下のようなプロジェクト作成ページが表示されるので適当なプロジェクト名を入力してCREATEボタンを押下します。
これでプロジェクトが作成されました。
同意画面を作成
次に画面上部から今作ったプロジェクトを選択し、左側のメニューからOAuth consent screenを選択します。
用途に応じてInternalあるいはExternalを選択してCREATEボタンを押下します。
次の画面ではApplication Nameに適当な名前を入力して画面最下部のSAVEボタンを押下します。
これで同意画面が作成されました。
クライアントIDを作成
次にクライアントIDを作成します。
左側のメニューからCredentialsを選択します。
続いて画面上部からCREATE CREDENTIALS > OAuth client ID
を選択します。
Application typeはOtherを選択し、適当な名前を入力してCreateボタンを押下します。
※最近の仕様ではOtherの代わりに「Desktop」があるようなので、Desktopがある場合にはこちらを選択してください
Client IDとClient Secretが表示されたら作成完了です。
使用するAPIを有効にする
最後に使用するAPIを有効にしておきます。
左側のメニューからLibraryを選択します。
使いたいAPIを検索してENABLEボタンを押下します。
今回はGoogle Sheets APIを有効にしました。
認証用ページを開いて認証コードを得る
さて次に前節で作ったクライアントIDを使って認証を行うプログラムを書いていきます。
認証を行うには、認証用のURLを生成して以下のような認証用ページをブラウザで開くように実装します。
認証用ページを開くには、URLhttps://accounts.google.com/o/oauth2/v2/auth
に認証用のクエリパラメータをくっつけたURLをブラウザで開くだけです。
URLはたとえばこんな感じになります。
https://accounts.google.com/o/oauth2/v2/auth?client_id=1234567890-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com&redirect_uri=http://localhost:64688&response_type=code&scope=https://www.googleapis.com/auth/spreadsheets.readonly&code_challenge=2EGy7-zr7sd9O09JLrNIIQ8Qf8wxUoJ-UAemn0yKMRk&code_challenge_method=S256&state=54l0lO2fQ71kC4Mas3auikqgkzZShA4ybFVDOLfUQ6U
パラメータについては以下で一覧表にし、複雑なものについては次節から詳しく説明します。
パラメータ名 | 説明 |
---|---|
client_id | クライアントID |
redirect_uri | 認証完了ページからリダイレクトするためのURI(後述) |
scope | 付与する権限(後述) |
response_type | code に設定しておけばOK |
code_challenge (optional) | 安全にOAuth2認証を行うために必要なもの(後述) |
code_challenge_method (optional) | Code Challengeの方法 |
state (optional) | リダイレクトしてきた結果が認証の結果であることに対する安全性を高めるための文字列(後述) |
このようにして開いたページでユーザが認証を完了すると、リダイレクトURIにアクセスが来ます。
認証コードはこのリクエストのクエリパラメータにcode
として埋め込まれています。
redirect_uri
redirect_uriパラメータにはユーザが認証し終わった後にリダイレクトするURIを指定します。
Unityエディタで使う場合、localhostの適当なポートを指定しつつ、
HttpListener
でローカルサーバを立ててリダイレクトを待ち受けます。
// 認証完了ページからのリダイレクトを待機する var httpListener = new HttpListener(); httpListener.Prefixes.Add(redirectUriWithSlash); httpListener.Start(); var taskCompletionSource = new TaskCompletionSource<IAsyncResult>(); httpListener.BeginGetContext(x => taskCompletionSource.SetResult(x), httpListener); var asyncResult = await taskCompletionSource.Task; var context = httpListener.EndGetContext(asyncResult); var request = context.Request; var response = context.Response; // 認証完了ページにメッセージを表示 var message = Encoding.UTF8.GetBytes($"<html><head><meta charset='utf-8'/></head><body>{CompletionMessage}</body></html>"); response.OutputStream.Write(message, 0, message.Length); response.OutputStream.Close(); httpListener.Close(); // 認証コードを取得 var code= request.QueryString.Get("code");
ローカルホストにアクセスが来たら、そのリクエストパラメータから認証コードを取得します。
上記で取得しているようにcode
として埋め込まれているパラメータが認証コードになります。
scope
scopeにはGoogleのどのAPIに対する認証を求めるかを指定します。
指定できるスコープは以下のページに一覧化されています。
たとえばSpreadSheetのRead権限だけを取得したい場合にはhttps://www.googleapis.com/auth/spreadsheets.readonly
を指定します。
また半角スペースで区切ることで複数のscopeを指定できます。
code_challenge
GoogleはOAuth2認証をよりセキュアに行う仕組みとしてProof Key for Code Exchangeを採用しています。
これはoptionalなパラメータですが、使用する場合には以下の手順でcode challengeを生成する必要があります。
- ランダムな英数字と「-」「.」「_」「~」で構成されたcode verifierを生成する
- code verifierのSHA256ハッシュを求める
- 2.のハッシュ値をBase64URLエンコードしたものをcode challengeとする
具体的な実装は以下のようになります。
public string GetCodeChallenge() { var codeVerifier = GetRandomStringForUrl(32); var codeChallenge = ConvertToBase64Url(Sha256(codeVerifier)); } private static string GetRandomStringForUrl(uint length) { var cryptoServiceProvider = new RNGCryptoServiceProvider(); var bytes = new byte[length]; cryptoServiceProvider.GetBytes(bytes); return ConvertToBase64Url(bytes); } private static string ConvertToBase64Url(byte[] bytes) { return Convert.ToBase64String(bytes) .Replace("+", "-") .Replace("/", "_") .Replace("=", ""); } private static byte[] Sha256(string source) { var bytes = Encoding.ASCII.GetBytes(source); return new SHA256Managed().ComputeHash(bytes); }
state
リダイレクトが確かにリクエストの結果であることを確認するための値です。
これもcode challengeと同様optionalなパラメータとなります。
送信時にはランダムな値などをstateとして送付し、
リダイレクト結果のパラメータのstate値と同一であることを以下のように確認します。
private void HandleCallback(IAsyncResult result) { var httpListener = result.AsyncState as HttpListener; var context = httpListener.EndGetContext(result); var request = context.Request; var state = request.QueryString.Get("state"); if (state != _state) { _handle?.Fail(new Exception($"OAuth error: Request has invalid state.")); return; } }
アクセストークンを取得する
前節のようにして認証コードが得られたら、次にアクセストークンを取得します。
アクセストークンは初回だけ認証コードを使って取得します。
期限が切れたらリフレッシュトークンを使ってリフレッシュします。
初回は認証コードを使って取得
Client IDとClient Secretからアクセストークンを得るには、https://oauth2.googleapis.com/token
にフォームをPostします。
var form = new WWWForm(); form.AddField("client_id", _clientId); form.AddField("client_secret", _clientSecret); form.AddField("code", authorizationCode); form.AddField("code_verifier", codeVerifier); form.AddField("grant_type", "authorization_code"); form.AddField("redirect_uri", redirectUri); var taskCompletionSource = new TaskCompletionSource<AsyncOperation>(); var request = UnityWebRequest.Post("https://oauth2.googleapis.com/token", form); request.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded"); request.SendWebRequest().completed += x => taskCompletionSource.SetResult(x); await taskCompletionSource.Task; var response = request.downloadHandler.text;
codeには前述の方法で取得したアクセストークンを使います。
code_verifierも前述の方法で作成したものをそのまま渡します。
レスポンスとして返ってくるjsonのaccess_tokenが含まれるので、これを各サービス使用時に使用します。
また同時にrefresh_tokenも受け取れますが、これは次節のリフレッシュ時に使うので保存しておきます。
期限が切れたらリフレッシュ
次に期限が切れたアクセストークンのリフレッシュを行います。
リフレッシュを行うには、grant_typeをrefresh_tokenに設定しつつ先ほど得られたリフレッシュトークンを送ります。
var form = new WWWForm(); form.AddField("client_id", _clientId); form.AddField("client_secret", _clientSecret); form.AddField("grant_type", "refresh_token"); form.AddField("refresh_token", refreshToken); var taskCompletionSource = new TaskCompletionSource<AsyncOperation>(); var request = UnityWebRequest.Post("https://oauth2.googleapis.com/token", form); request.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded"); request.SendWebRequest().completed += x => taskCompletionSource.SetResult(x); await taskCompletionSource.Task; var response = request.downloadHandler.text;
レスポンスとして返ってくるjsonのaccess_tokenに新しいアクセストークンが返されます。
実装例
以上で説明は終わりとなりますが、実際に実装した例を以下のリポジトリに用意しました。
必要に応じて参照してください。