🦄

シリアルコード認証(カスタム認証) - UnityでSDKなしでFirebaseを使う

2021/07/02に公開

Firebase Authenticationでは基本的な機能としてメールアドレスとパスワードによる認証、電話番号認証、OAuthを利用した認証などが用意されています。基本的な認証方式以外の方法(例えばIDとパスワードによる認証やシリアルコードによる認証など)はカスタム認証機能を使って作成する事ができます。

今回はシリアルコードを利用した認証の仕組みを作成する方法を解説していきます。

この投稿はUnityでSDKなしでFirebaseを使うの一部です。

途中で出てくるソースコードの全体は以下にあります。
Unity側:
https://github.com/satouso0401/firebase-unity-not-use-sdk/tree/main/Assets/Scenes/Authentication

Firebase側:
https://github.com/satouso0401/firebase-unity-not-use-sdk-firebase

システムの構成

今回は以下の構成の認証システムを作成します。

シリアルコードとカスタムトークンの交換Functionの作成

はじめにFirebaseの各種設定とFirebase Functionの実装を行なっていきます。

IAM API の有効化

トークンに署名する際にIAM Service Account Credentials APIを利用するので、APIを以下のURLから有効化します。
https://console.developers.google.com/apis/api/iamcredentials.googleapis.com

カスタムトークン作成権限の設定

Firebase Functionsの実行時に使用されるアカウントにカスタムトークンの作成権限を付与します。

まず以下のURLからGCPのIAMのページに移動します。
https://console.cloud.google.com/iam-admin/iam?consoleUI=FIREBASE

名前がApp Engine default service accountとなっているサービスアカウントのメンバーを編集します。

サービスアカウントトークン作成者のロールを追加して保存します。

トークン発行Functionの作成

ローカルのFirebaseプロジェクトのfunctionsディレクトリ配下にシリアルコードとカスタムトークンを交換する処理を実装していきます。
プロジェクトを作った際にサンプルコードのが書かれたindex.tsファイルが作成されていますが以下のコードに書き換えます。
将来的にfunctionを複数作成する際にfunctionごとにtsファイルを作成したいので、index.tsには直接処理を書かず他のファイルの処理が呼ばれるように実装しています。

functions/src/index.ts
import * as functions from "firebase-functions";
import * as express from "express";
import * as admin from "firebase-admin";

// eslint-disable-next-line @typescript-eslint/no-var-requires
const customAuth = require("./handlers/custom-auth");

admin.initializeApp();
const expressApp = express();

expressApp.get("/", customAuth.handler);
exports.customAuth = functions
    .region("asia-northeast1").https.onRequest(expressApp);

トークンの交換処理本体は以下の実装になります。
以下の実装では、わかりやすさのためシリアルコードとユーザーIDはリテラルになっていますが、Firestoreにシリアルコードを持たせる実装のサンプルコードもGitHubのリポジトリで公開しています。

functions/src/handlers/custom-auth.ts
import * as express from "express";
import * as admin from "firebase-admin";

exports.handler = async (req: express.Request, res: express.Response) => {
  const serialCode = "xxxx-yyyy-zzzz";
  const uid = "uid-0001";
  // Unity側からはURLクエリパラメーターのserialCodeにシリアルコードを設定してリクエストする
  if (req.query.serialCode == serialCode) {
    admin
        .auth()	
	// ここでFirebase Authenticationにuidのユーザーが追加されて、サインイン用のトークンが返ってくる
        .createCustomToken(uid)
        .then((customToken) => {
	    // customTokenをJsonレスポンスとして返す
          res.status(200).json({customToken: customToken});
        })
        .catch((error) => {
          console.log("Error creating custom token: ", error);
          res.status(500).json({message: "トークン作成失敗"});
        });
  } else {
    res.status(500).json({message: "トークン作成失敗"});
  }
};

コードができたら、firebase deploy --only functions コマンドを実行してデプロイします。

Functionのデプロイの確認

Firebaseの管理画面でサイドメニューでFunctionsを選ぶとデプロイされたFunctionが確認できます。

表示されているURLの末尾に?serialCode=xxxx-yyyy-zzzzをつけてブラウザからアクセスした場合に、Jsonが返って来ることが確認できればFunctionの作成は完了です。

urlの例:

https://[プロジェクトID].cloudfunctions.net/customAuth?serialCode=xxxx-yyyy-zzzz

レスポンスの例:

{"customToken": "****~700文字前後~****"}

Unityからの認証

Firebase側の準備ができたのでUnity側でカスタム認証を行う処理を実装していきます。

サンプルコード:

Assets/Scenes/Authentication/CustomScript.cs
// APIキーはFirebaseのサイドメニューの「プロジェクトの概要」右側の歯車の「プロジェクトを設定」を開くと「ウェブ API キー」の部分に表示されています
private string _apiKey = "APIキーを設定してください";

public void CustomTokenSignin()
{
    var inputSerialCode = GameObject.Find("InputSerialCode").GetComponent<InputField>().text;
    var resultText = GameObject.Find("ResultText").GetComponent<Text>();

    var customToken = GetCustomToken(inputSerialCode);
    var signInResult = SignInWithCustomToken(customToken);

    Debug.Log($"idToken: {signInResult.idToken}");
    Debug.Log($"refreshToken: {signInResult.refreshToken}");
    resultText.text = "サインインに成功しました";
}

[Serializable]
class GetCustomTokenResponse
{
    public string customToken;
}

string GetCustomToken(string serialCode)
{
    WebClient wc = new WebClient();
    wc.Headers[HttpRequestHeader.ContentType] = "application/json";
    // urlには「Functionのデプロイの確認」で動作確認したURLを設定します
    var url =
	$"https://[プロジェクトID].cloudfunctions.net/customAuth?serialCode={serialCode}";
    var response = wc.DownloadString(new Uri(url));
    var customTokenResponse = JsonUtility.FromJson<GetCustomTokenResponse>(response);
    return customTokenResponse.customToken;
}

FirebaseApi.SignInWithCustomTokenResponse SignInWithCustomToken(string token)
{
    WebClient wc = new WebClient();
    wc.Headers[HttpRequestHeader.ContentType] = "application/json";
    var url = $"https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key={_apiKey}";
    var requestBody = new FirebaseApi.SignInWithCustomTokenRequest(token);
    var json = JsonUtility.ToJson(requestBody);
    var response = wc.UploadString(new Uri(url), json);
    return JsonUtility.FromJson<FirebaseApi.SignInWithCustomTokenResponse>(response);
}

GetCustomToken関数は前述の手順で作成したFunctionを使ってFirebase Authenticationへのサインイン用のカスタムトークンを取得する処理です。

SignInWithCustomToken関数はFunctionから返ってきたカスタムトークンを使ってサインインする処理です。サインインに成功するとFirebaseの様々なサービス横断で認証に使用できるidTokenrefreshTokenを取得できます。

参考

https://firebase.google.com/docs/auth/admin/create-custom-tokens?hl=ja

Discussion