Unity
Auth0
aws-serverless-express

初心者がAuth0+AWSでサーバレスSNSログインを作ってUnityアプリと連携してみた話

はじめに

Unityでアプリを作って、ユーザーにログインしてもらいたいこと、ありますよね(たぶん)。
本業のゲーム開発者の方などは、もちろん独自の認証システムやユーザーデータベースを用意されると思います。
でも、同人的に遊んでみたい方などは、そこまでやるのはちょっと大変(たぶん)。
というわけで、今回はそんなニッチな路線を狙って、試してみたことを備忘録もかねてまとめてみます。

初投稿なので自己紹介!

私は出版社勤務の(普通の)編集者で、週刊誌を作ったり、小説の単行本を担当したりしてきました。
が、最近はエンジニアの方々に集まっていただいてチームを作り、ウェブメディアを中心とするサービス開発を「なるべく内製化しよう」という運動をしています。
紙の本だけに向き合うのでは未来像が描きにくい時代、コンテンツを届ける仕事をするなら、独自開発もできなきゃダメよね!という気持ちです。

年末年始に挑戦してみたことがあり、せっかくなのでできたことをQiitaに投稿してみたいと思います!
※2019/01/05:書き上がったら、知り合いのエンジニアの方々にレビューをお願いしようと思うので、現時点ではまだ暫定版としておきます。
※2019/01/05:素人の書いたものですので、誤解や無意味な部分、セキュリティリスクなどが含まれている可能性もあります。今後もブラッシュアップしていくつもりですが、あくまで「やってみた」記事だとお考えください。

目次 内容
この記事で扱う内容 実現すること/具体的に行うこと
何がしたかったか ※読み飛ばし可
そもそも、「Auth0」とは 選定の理由
Auth0のセッティング APIとApplicationの作成
Auth0のライブラリ「Lock」でログインウィジェットを作ってみる あっという間にログインウィジェットが完成
AWSでaws-serverless-expressを組んでみる AWS CLIのセットアップ/aws-serverless-expressのセットアップ
aws-serverless-expressにAuth0のログインウィジェットを組み込む ログインウィジェットをpug化
Callbackページの設定 Auth0の設定/callbackページもpugで作る
app.jsを書き直す npmの追加/access_tokenを検証する/検証が通ったらcallbackページをレンダリング
動作確認 ログイン --> ボタンを押してjsがちゃんと動くことの確認
独自ドメインの適用 CloudFrontで独自ドメインを当てる
SNSなどOpenIDでのログインを追加 Googleの設定/Facebookの設定
Unityからアクセスしてみる EmbeddedBrowserを入れてURLを指定する/サイズの調整/callbackは動作するか?
本当はやるべきこと セキュリティについても考えたかった

この記事で扱う内容

実現すること

  • ユーザーに、UnityアプリからID/PASSやSNS(Facebook,Twitter...)でログインをしてもらう
  • ログインしたユーザーだけが、次のシーンに進めるようにする

具体的に行うこと

  • 認証サービスAuth0をセッティング
  • AWSのaws-serverless-expressでAPI GatewayとLambda一式をサクサク作成
  • UnityのEmbedded Browserを利用してログインUIを作成
  • ログインした人だけが見られるLambda経由でUnityのシーンを切り替える

何をしたかったか(※読み飛ばし可)

もともと興味があったのは、Unityを使ってMultiPlayer Gameを作ることでした。
バーチャル空間を自由に作れるようになれば、今後、いろいろなオモシロ会員制ビジネスも作れるよね!というボンヤリした夢があるからです。
そんな中、2018年12月リリースのUnity2018.3では、スクリプティング・ランタイムとしてこれまで標準だった.NET3.xにかえて、.NET4.xも使えるようになりました。
https://unity3d.com/jp/unity/whats-new/unity-2018.3.0
たしか2018.1以降くらいであれば、Settingを変えれば4.xも実験的には利用できていたと思うのですが、ついに「実験的」という文言がはずれたのです。

一方で、Auth0+Unityで問題となってきたのが、この.NETのバージョン問題でした(と、勝手に思っている)。
例:「https://community.auth0.com/t/how-to-implement-auth0-in-unity-game-engine/8796」

というわけで、「Auth0の.NET SDKがそのまま使えるんじゃ?」と考えたのが、年末年始にこの課題に手をつけたキッカケでした。
ただ、そっちのほうは初心者としては、まだまとめられるほどの内容がありません。
なので、先に簡単に動かせたほうをまとめてみました。

そもそも、「Auth0」とは

こちらの記事→「認証プラットフォーム Auth0 とは?」でAuth0日本法人の古田さんがまとめてくださっていますが、「ログインしてもらう」サービスを、さまざまなレベルで、ポチポチ、サクサクで作れるサービスIDaaS(Identity as a Service)です。
古田さんのQiitaには、Auth0のdefaultの選択肢にはないLINEアカウントでのログインを追加する方法(「Auth0でLINEログインを実装してみた (v2.1対応)」)など、おもしろい記事がいろいろあるので、ぜひ参考になさってください!

私自身は、なぜ今回、Auth0を使ってみたかというと、

  • さまざまなIdP(Id Provider、GoogleとかFacebookとかTwitterとか)が簡単につなぎこめる
  • AWSのリソースとの相性がよい(今回は扱いませんが、Cognitoにもつなぎこめるので、UnityのAsset BundleをS3において、特定のユーザーだけアクセス可能にする、といったこともできるはず)
  • いろいろマネージドな形でリソースを提供してくれるので、開発コストが低い(ログインウィジェットもすぐ実装できる)

といった理由があげられますが、実は仕事でご縁があったから自分でも触ってみたかった、というのが一番大きいです(笑)。

Auth0のセッティング

Auth0は、全般的にドキュメントが大変しっかりしたサービスなので、ググるといろいろなガイダンスが出てきます。
無料でアカウントを作っても、かなり充実した機能が使える印象です。
参考:「クラウド認証サービス Auth0 の無料トライアルを試してみよう」

アカウントを作成 > テナントを作成

までは、上記を参考に進めてください。
この状態だと、defaultのapplicationとAPIしかない状態だと思います。お試しなので、そのまま作業を進めても構わないのですが、念のため、テスト専用のAPIとapplicationを用意します。

先にAPIをポチポチと作りましょう。メニューのAPIsに進みます。
Dashboard > APIs > CREATE API
CREATE API

すると、下のようなモーダルが出てきます。
Nameは自分にわかりやすいものでOK。
Identifierは注意書きにもあるように「URLの形式をとっていればいいだけ」で、実際に外部からアクセス可能なURLである必要はないので、適当なURLを書いてください。
ただし、あとから変更はできないので、独自ドメインを取っている方などは、できれば自分が管理しているドメインのURLを書けるといいのではないかと思います。
Signing Algorithmは、OpenIDでソーシャルログインと連携したいので、RS256にしておくほうがよいようです。
(※あとで出てくるaccess_tokenの形式とも関係しています)
NewAPI
これでAPIがひとつできます。
(APIなのにAppっていう名前にしてもうた…汗)
このままだと、このAPIはオールマイティで、どこからでも叩ける上に、いろいろできてしまうので、本来はscopeで必要最小限のroleだけ使うようにセッティングしたり、Rulesでアクセスの制限を行ったりする必要があります。
今回は私が力尽きたので、そこの記事化は取りこぼしという感じです。

次にapplicationを作ります。
Dashboard > Application > CREATE APPLICATION
CREATE APP

すると、下のような選択肢が出てきます。
今回はLambda経由であれこれ取り回しを行うので、Machine to Machine Appとして作成してみます(他でもいいかもしれない)。
NewApp

認証に使うAPIを選択する画面になるので、先ほど作ったAPIを選びます(どれを使うかは、あとからSettingで変えられます)。
Select API

今回は、FacebookなどSNS系のIdPとの連携などは、全体の仕組みを組み上げてからセッティングします。

Auth0のライブラリ「Lock」でログインウィジェットを作ってみる

ここまで完全にポチポチで進んできました。
「で、実際このApplicationとどうやってやりとりするの?」
ということなんですが、工数が少なくて済むのは、Auth0が提供しているライブラリ「Lock」(https://auth0.com/docs/libraries/lock/v11)でログイン&サインアップフォームを作ってしまうことではないかと思います。

Lockで作られるウィジェットのデフォルトの状態は上記のページなどで確認していただけますが、そのままだと明らかにAuth0なので、オリジナルサービスとしては少し悲しいです。
ところがLockでは、optionを指定するだけで、ちょっとしたカスタマイズがごくごく簡単に行えます。
https://auth0.com/docs/libraries/lock/v11/configuration

しかも今回は、UnityのEmbedded Browserでログインフォームを表示させて使うことを考えていますので、シンプルにログインフォームだけの画面が作れれればOK。
というわけで、試しに、下記のようなログインのページを作成します。
ライブラリのページの最下部に、コードのサンプルがありますが、inline用のコードほぼそのまんまです。

login.html
<head>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div id="root" style="width: 320px; margin: 40px auto; padding: 10px; box-sizing: border-box;">
    embedded area
</div>
<script src="https://cdn.auth0.com/js/lock/11.6.1/lock.min.js"></script>
<script>
  var lock = new Auth0Lock('YOUR_CLIENT_ID', 'YOUR_AUTH0_DOMAIN', {
    container: 'root',
    language: 'ja',
    languageDictionary: {
        title: "LOGIN"
    },
    theme: {
    logo: 'https://YOUR_DOMAIN/logo.png',
    primaryColor: '#33cccc'
    },
    socialButtonStyle: 'small',
    auth: {
      redirectUrl: 'YOUR_REDIRECT_URL',
      responseType: 'token id_token',
      params: {
        scope: 'openid email'
      },
      audience: 'YOUR_AUDIENCE',
      responseMode: 'form_post',
    }
  });
  lock.show();
</script>
</body>

YOUR_CLIENT_IDYOUR_AUTH0_DOMAINaudience: 'YOUR_AUDIENE'については後述します。
YOUR_REDIRECT_URLは、認証後にcallbackされてくる先のURLですが、この段階ではまだ指定できませんので、ひとまず放置します。
responsModeは、のちにAWSのaws-serverless-expressでセッティングするときに効いてくるおまじないです。
(なんて思わせぶりをできるような玄人さんの身分ではないので…。これは認証後のredirect時に、tokenをPOSTすることを明示するセッティングです)

そのほかのパーツは、見た目のカスタマイズ用のoptionです。
language: 'ja'でUIを日本語化できます。余計ごとながら、化けるのでhead > metaで文字コードはちゃんと指定しましょう。
languageDictionaryはUIの各パーツの文言などをいじる部分(だと思う)ですが、ここではtitleを変えました。デフォルトだと「Auth0」と書いてあります。
logohttps://YOUR_DOMAIN/logo.pngは、どこかオンラインにホストしたログインフォーム用のロゴ画像です。
高さ58pxが推奨とされています(この辺りも上のoptionの説明の中で詳述されています)。
指定しない場合は、Auth0のロゴマークが出てきます。
primaryColorはパーツの基調色です。Auth0のデフォルトは朱色。ここはお好みでどーぞ。

これを実際に表示させると、下記のようなページになります。
login form

めっちゃ便利。
Googleでのログインボタンが出ているのは、Auth0がデフォルトのセッティングで「Googleもつけるとこうなるよ」というサンプルを入れ込んでいるからです。
あとでSNSログインを調整するときに整えていきます。

上記で説明を後回しにしたYOUR_CLIENT_IDYOUR_AUTH0_DOMAINですが、これはAuth0のコンソールで確認できます。
Dashboard > Applicationで先ほど作ったAppを選ぶと出ています。
clientid
実は、ここを開かなくても、ドメインは右上の自分のアカウントのボタンの横に表示されている<テナント名>.auth0.comです。
また、Client IDも他の場所にも表示されているんですが、ここを開くとコピーボタンがあるので楽ですよね。

もうひとつ、重要なのがaudienceです。
Auth0のいろいろなチュートリアルをやると、audienceに~~~~/userinfoといった形のURLを入れてみましょう、といったサンプルがありますが、今回は、それはやめておきましょう
audienceに上記の形のURLを入れると、返ってくるaccess_tokenがjsonにならないというドツボがあるようです。
あとあと、Lambdaの中でパースするときに不便なので、audienceには次の値を入れましょう。
Dashboard > APIsで先ほど使うことにしたAPIを選びます。
APIidentifier
はい。これはAPIを作るときに書いた、適当なURLです(笑)。
このように、コードの中に出てきてしまうので、あんまりふざけた責任の持てないURLを書いてしまわないようにしたほうがよさそうですよね。

AWSでaws-serverless-expressを組んでみる

さて、ここまでで作ったようなログインページを、S3でもなんでもいいのでホスティングすれば、ログインのフローを作っていくことはできます。
ただ、Unityアプリに埋め込んだブラウザからしか使わないページをわざわざホスティングしつづけるのも、なんだかつまんないかなあ、と思ったので、サーバレスな構造をいろいろ試してみました。

  • API Gatewayにlambdaで書いたcustom authorizerを噛ませて、cognito連携してIAMのロールを振り出す
  • API Gatewayのcustom authorizerとして、Auth0をIdPとして連携したcognitoを指定してみる

最初に挑戦したのがこういう系のAWS内の権限を取り回すようなやり方だったんですが、うまくいかなかったんですね。
そもそも、初心者なんで、まだそこまで実力がなかった、というのが一番の理由。その代わりめっちゃ勉強にはなりました。
あと、少しだけ言い訳すると、「簡単にできる」と標榜するには、ちょっと大変かなあと感じてしまいました。

そんな中で、Auth0のドキュメンテーションを見ていて、「あー、なんかこれいけるんじゃない?」と思ったのが、Node.jsのexpressで攻める方法でした。
そもそも、Auth0のOpenID Connect(OIDC)のフローを回すと、access_tokenとid_tokenが取れます。
そのvalidateをするサンプルが、みんなexpressで書いてあったんですよね。

※「OpenIDでtokenが取れるとかどうとかって、なに?」っていう方は、下記のページがホントにわかりやすいです。いろんな記事で引用されています。
「一番分かりやすい OAuth の説明」
「一番分かりやすい OpenID Connect の説明」

※単純に、expressはめちゃくちゃスタンダードなNode.jsのフレームワークだというだけかもしれないけど、そこで暴走してしまうのが素人の強み(笑)。
上記で、サンプルがexpressで書いてある、と言ったのは、たとえば下記のようなページです。
「Server Client + API: Node.js Implementation for the API」

AWS CLIの準備

※すでにAWS CLIをバリバリご利用の方は、このセクションは飛ばしてください。

この部分は、次の記事を参考にさせていただきました!
「AWS CLIのインストール」
私自身はmacユーザーなので、基本macを前提に書きますが、参考記事はみなさん他のOSについても解説してくださっているので、参考になさってください。
そもそものAWSのアカウント取得などなどは、たくさん記事があるので、探してみてください。

STEP1: pipのインストール

pipはpythonベースのパッケージ管理ツール、であるそうです。

ターミナル
curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
sudo python get-pip.py

pythonをお使いの方は、すでに入っている可能性もあります。macOSの場合は、デフォルトのpythonだと入っていないようです。
python自体をきっちりインストールしたい方は、こちらの記事をどうぞ。pyenvで入れると、pipがついてくるようです。
- 「Pythonインストール(Mac編)」※この記事は古いということで、新版が出ています。ただ、そちらではpipへの言及はありません。
- 「Python3インストール(Mac編)」
- Windowsでのpipのインストールについて(https://qiita.com/suzuki_y/items/3261ffa9b67410803443

STEP2: AWS CLIのインストール

ターミナル
sudo pip install awscli --upgrade --ignore-installed six

STEP3: AWSの設定確認

configを行うのに必要な情報を取得していきます。ここは公式ドキュメント(https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-configure.html)がわかりやすいです。
AWSコンソール > IAM > ユーザーと進み、使いたいユーザー名をクリックします。
タブの中から、認証情報を選ぶと、アクセスキーの生成があります。
ここから、アクセスキーシークレットアクセスキーのペアが得られます。

STEP4: configの設定

ターミナル
aws configure

と叩いてやると、下記の設定を順番に聞かれます。
STEP3で生成したアクセスキーシークレットアクセスキーを入れていきます。
regionは、日本のユーザーの方はたいてい「東京(ap-northeast-1)」だと思いますが、違うかたはご自身の環境に合わせてください。
output formatjsonとしておけばよいようです。

ターミナル
AWS Access Key ID [None]: hogehogehogehoge
AWS Secret Access Key [None]: fugafugafugafuga
Default region name [None]: ap-northeast-1
Default output format [None]: json

あと、アップデートなどその他の手順もあるのですが、それは元記事で書いてくださっているのを参考になさってください。
これで一応、AWSリソースをCLIで使えるようになりました!

aws-serverless-expressを使ってみる

この部分は、次の記事を参考にさせていただきました!
「AWS 謹製 aws-serverless-express を使って APIGateway + Lambda + Node + Express で RESTful サービスの雛形を最速で作る」

参考というか、調査中にこの記事に出会わなかったら、これをやってみませんでした。伏して感謝します!
非常にわかりやすく書いてくださっているので、初心者でもすぐにサーバレスでexpressを使える環境が作れました。

ただ、その後、Lambdaやaws-serverless-expressの仕様が少し変わっている部分もあるようだったので、その辺りを順を追って書いてみます。
私の説明がすっ飛んでいるところもあると思いますので、上記記事はぜひご一読ください!

STEP1: Node.jsの環境設定

上記の参考記事の時点と、Lambdaが対応しているNode.jsのバージョンが変わっているようだったので、私は一番新しいバージョンで環境を設定してみました。
2019/01/04時点で、Lambdaの対応している最新のNode.jsは、v8.10となっています(Lambdaのコンソールを見ると、そうなっていました)。

ターミナル
nodebrew use v8.10.0

STEP2: git cloneしてくる

AWSが提供してくれているaws-serverless-expressのリポジトリはこちら。ドキュメンテーションも確認しながら作業するとよいようです。
https://github.com/awslabs/aws-serverless-express

作業するフォルダを仮にtestAppとします。

ターミナル
git clone https://github.com/awslabs/aws-serverless-express.git
cp -r ./aws-serverless-express/examples/basic-starter ~/testApp
cd ~/testApp

参考記事の時点では、exampleはひとつだったようなのですが、この記事を書いている時点では「examples」になっており、参考記事で紹介されているものは「basic-starter」のほうのようです。

STEP3: config設定

ターミナル
npm run config -- --account-id="<accountId>" --bucket-name="<bucketName>" --region="ap-northeast-1" 

アカウントIDは、AWSコンソール > 右上の自分のアカウント情報(アカウント)を開くと、最上部に出ている文字列(数列?)です。
regionを最後に指定していますが、これはデフォルトだと米国西部になってしまうようなので、必ず指定しましょう。
バケット名は、これからアップロードするソースの置き場なのですが、ここで割とハマりました。

バケット名には、「世界でただひとつの名前」をつける必要があるらしいのです
最初、私は「serverless-express-test」などと、なんとなくありそうな名前にしていたら、一向にCloudFormationが成功しませんでした

しかも、このnpm run configは「一発勝負」で、最初の1回しか効きません。
ここで設定したものをやり直したい場合は、フォルダ内のpackage.json,simple-proxy-api.yaml,cloudformation.yamlを手動で修正しないといけないのです。
(とはいえ、バケット名が出てくるのはpackage.jsonだけだったけど)

npm run configが一発勝負だよ、といったことは、githubのリポジトリのexample/basic-starterで説明されています。
https://github.com/awslabs/aws-serverless-express/tree/master/examples/basic-starter

というわけで、バケット名には、およそ世界の誰も書かないような名前をつけてconfigを行ってください(笑)!
(私は結局、自分の名前をローマ字で平打ちして末尾に追加しましたw)

STEP4: いよいよセットアップ!

ドキドキです!

ターミナル
npm run setup

成功パターンの場合、ちょっと待っていると、何事もなかったようにターミナルが落ち着きます。
そこで、AWSコンソール > CloudFormationを確認してみましょう(「管理とガバナンス」の中にあります)。
なんかできてる!
cloudformation

うれしい。
このスタックの名前を変更したい場合は、npm run configの最後に--function-name="<functionName>"として指定すれば、好きなものにできます。

stack

スタック名をクリックすると、こんな画面に出ます。
ApiUrlにアクセスしてみてください。
aws-serverless-sample.png

かわいい。
スクショでは下のほうは切れてしまっていますが、このサンプルページには、簡単な会員情報をGET/POSTするendpointが列挙されています。

STEP5: 修正反映方法の確認

これで、サーバレスでexpressを使う準備はできました。いやー、すごい。勢いだけで、ここまで来られる時代が来た!
ここから、basic-starterをちょこっといじるだけでログイン画面を作ってしまおうという魂胆なのですが、このApiGateway+Lamdaのシステムを修正した場合に、反映する方法を確認しておきます。

ターミナル
npm run package-deploy

こちらです!
参考記事のときと、更新用のコマンドが変わっているようなので、ご注意ください(githubの説明はこちらになっていました)。

aws-serverless-expressにAuth0のログインウィジェットを組み込む

ここまで来たら、あと一息です。
aws-serverless-expressのbasic-starterはどういう構造になっているのかみてみると、

basic-starter
|
|--app.js
|--lambda.js
|--package.json
|--その他の設定ファイル
|
|--node_modules
|  |--いろいろなnpm
|
|--views
   |--index.pug

となっています。appの挙動を変えていくには、通常はapp.jsを修正していきます。
lambda.jsなどには手を入れる必要はありません。

ただ、Auth0でのログインをサクサク組み込んでいけると気分的に「アガる」と思うので、先走って、前半で作成したAuth0のログインウィジェットをここに組み込んで表示させてしまいましょう。

素のAPIのエンドポイントを叩いたときの、リスがニコニコしているかわいい画面はどこで作られているかと見てみると、index.pugです。
「pugってなんじゃい」という方は、こちらの記事などが参考になります。
「Pug(Jade)って何だ?特徴や基本的な使い方の解説」
旧名「Jade」といったそうですが、htmlを効率的にコーディングするためのテンプレートエンジン、ということのようです。

といっても、もともとhtmlだったものをpug化するのは、割と簡単です。
私は、こちらのページなどを参考に手で書き直してみました。
「Pug(Jade)記法でHTMLのテンプレート的なの」

ただ今回は、pugの記法の哲学とか美しさを度外視して、まずは目的に最短で近づこう、ということなので、ちょっとチートして変換ツールを使ってもいいかと思います。
下記のサイトは、Jade時代に作られたということで、必ずしもpugに対応しているわけではない、といった注意書きのある記事も見かけたのですが、今回使う分には困らないようでした。
「https://html2jade.org/」
HTMLを放り込むとJadeにしてくれる、というツールです。

変換ツールを使うか使わないかはお好みですが、前半で作ったログインウィジェットをpug化してみると、こんなふうになります(サンプルは手動で書き直したものです)。

login.pug
head
  meta(name="viewport" content="width=device-width, initial-scale=1")
  meta(http-equiv="Content-Type" content="text/html; charset=UTF-8")
  style.
    div#root {
      width: 320px;
      margin: 40px auto;
      padding: 10px;
      box-sizing: border-box;
    }
body
  div#root
    embedded area
  script(src="https://cdn.auth0.com/js/lock/11.6.1/lock.min.js")
  script.
    var lock = new Auth0Lock('YOUR_CLIENT_ID', 'YOUR_AUTH0_DOMAIN', {
    container: 'root',
    language: 'ja',
    languageDictionary: {
        title: "LOGIN"
    },
    theme: {
    logo: 'https://YOUR_DOMAIN/logo.png',
    primaryColor: '#33cccc'
    },
    socialButtonStyle: 'small',
    auth: {
      redirectUrl: 'YOUR_REDIRECT_URL',
      responseType: 'token id_token',
      params: {
        scope: 'openid email'
      },
      audience: 'YOUR_AUDIENCE',
      responseMode: 'form_post',
    }
    });
    lock.show();

注意点としては、pugの記法ではタグの閉じがない代わりに、インデントが割と重要な意味を持つらしい、ということで、htmlでの階層構造に合わせてきれいにインデントしてあげることが必要なようです。

また、上のサンプルには入れていませんが、パラメーターのセクションにredirectUrl: 'YOUR_REDIRECT_URL' //これがコールバックなどといったコメントを入れていると、エラーになるという記事も見かけました(実際、私もコメントが入っているpugではうまく表示ができませんでした)。

では、/testApp/basic-starter/views/index.pugを、上で作ったlogin.pugと入れ替え、login.pug --> index.pugとリネームしてみましょう。
その上で、システムを更新します。

ターミナル
npm run package-deploy

先ほどのAPIのエンドポイントにアクセスすると…
endpoint
イエイ!
これはアガりますね。

Callbackページの設定

Auth0での設定

ここまでずっと放置してきたのが、ログインウィジェットのredirectUrlです。
そこで、index.pug(旧login.pug)内のcallbackのエンドポイントをredirectUrl: 'https://<YOUR_API_PREFIX>.execute-api.ap-northeast-1.amazonaws.com/prod/callback'としてみます。

Auth0では、Dashboard > Application > Settingsの中に、callbackとして許可するURLを記載する必要があります。
callbackurl

コンマ区切りで必要なURLを追加することができるので、開発環境や独自ドメインを当てる前のAPI Gatewayなども入れておくことができます(完成したら不要なものは消したほうがよいでしょう)。
※独自ドメインを設定される方は、index.pug内に記述するべきredirectUrlも変わってしまうので、最後に全体の整合性が取れるように調整してください。

このタイミングで、Allowed Web Originなども記述しておくとよいかもしれません。

Callbackのページもpugで作っておく

今回は、Unityのsceneを進めるよう、SceneManagerと連携するボタンをつけるのが目標ですが、実体としては単にjavascriptを発動させるボタンができればいいので、サンプルとして、次のようなpugを用意します。

callback.pug
html
  head
    meta(name='viewport', content='width=device-width, initial-scale=1')
    meta(http-equiv='Content-Type', content='text/html; charset=UTF-8')
    style.
      input {
        width: 16rem;
        height: 4.5rem;
        margin: 0 auto;
        background-color: #33cccc;
        color: #fff;
        font-size: 20px;
      }
    script(type='text/javascript').
      function startGame(){
      alert('hello world!');
      }
  body
    #button-area(style='width: 320px; margin: 40px auto; padding: 10px; box-sizing: border-box;')
      input(type='button', value='StartGame', onclick='javascript:startGame();')

ボタンを押すと、「hello world!」というやつです。よくあるパターン。
button-areaのstyleがベタ書きなところが、先ほどのindex.pug(元login.pug)と違いますが、多分callback.pugのほうが「美しくないpug」です。
でも、どちらでもちゃんと動いてくれます。

このcallback.pugも、index.pugと同じ/basic-starter/viewsフォルダに入れておきましょう。

app.jsを書き直す

見た目の部分はviewsフォルダに用意できたので、次に機能の部分を調整していきます。
主にいじるのはapp.jsですが、いくつか考えておくべきことがあります。

npmの追加

前半戦で、わかりやすいOAuthとOpenIDの解説記事について触れました。
あの記事を読むと一目瞭然なのですが、ログインが済んだら、クライアントが受け取ったaccess_tokenを検証しなければいけません。
Auth0が提供しているこちらのドキュメント(「Server Client + API: Node.js Implementation for the API」)のサンプルには、こんなコードが出ています。

sample1
// set dependencies
const express = require('express');
const app = express();
const jwt = require('express-jwt');
const jwksRsa = require('jwks-rsa');
const bodyParser = require('body-parser');

// enable the use of request body parsing middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: true
}));

// create timesheets upload API endpoint
app.post('/timesheets/upload', function(req, res){
  res.status(201).send({message: "This is the POST /timesheets/upload endpoint"});
})

// launch the API Server at localhost:8080
app.listen(8080);
sample2
// set dependencies - code omitted

// enable the use of request body parsing middleware - code omitted

// Create middleware for checking the JWT
const checkJwt = jwt({
  // Dynamically provide a signing key based on the kid in the header and the singing keys provided by the JWKS endpoint.
  secret: jwksRsa.expressJwtSecret({
    cache: true,
    rateLimit: true,
    jwksRequestsPerMinute: 5,
    jwksUri: `https://YOUR_AUTH0_DOMAIN/.well-known/jwks.json`
  }),

  // Validate the audience and the issuer.
  audience: process.env.AUTH0_AUDIENCE,
  issuer: `https://YOUR_AUTH0_DOMAIN/`,
  algorithms: ['RS256']
});

// create timesheets API endpoint
app.post('/timesheets/upload', checkJwt, function(req, res){
  var timesheet = req.body;

  // Save the timesheet entry to the database...

  //send the response
  res.status(201).send(timesheet);
})

// launch the API Server at localhost:8080 - code omitted

これを応用するためには、まずexpress-jwtjwks-rsaのnpmを入れておく必要があります。
もともと、aws-serverless-expressには、expressbody-parserは入っているんですよね。
なぜそれがすぐわかるかというと、デフォルトのapp.jsがそもそも、こんな作りだからです。

body-parserの初期化もjsonとurlencodedで別々にやるとか、基本の所作がもう済んでいる。
こういう枠組みの使い回しがきく、というようなところが強みなんだろなー。

app.js(default)
'use strict'
const path = require('path')
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const compression = require('compression')
const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware')
const app = express()
const router = express.Router()

app.set('view engine', 'pug')

if (process.env.NODE_ENV === 'test') {
  // NOTE: aws-serverless-express uses this app for its integration tests
  // and only applies compression to the /sam endpoint during testing.
  router.use('/sam', compression())
} else {
  router.use(compression())
}

router.use(cors())
router.use(bodyParser.json())
router.use(bodyParser.urlencoded({ extended: true }))
router.use(awsServerlessExpressMiddleware.eventContext())

// NOTE: tests can't find the views directory without this
app.set('views', path.join(__dirname, 'views'))

router.get('/', (req, res) => {
  res.render('index', {
    apiUrl: req.apiGateway ? `https://${req.apiGateway.event.headers.Host}/${req.apiGateway.event.requestContext.stage}` : 'http://localhost:3000'
  })
})

router.get('/sam', (req, res) => {
  res.sendFile(`${__dirname}/sam-logo.png`)
})

router.get('/users', (req, res) => {
  res.json(users)
})

router.get('/users/:userId', (req, res) => {
  const user = getUser(req.params.userId)

  if (!user) return res.status(404).json({})

  return res.json(user)
})

router.post('/users', (req, res) => {
  const user = {
    id: ++userIdCounter,
    name: req.body.name
  }
  users.push(user)
  res.status(201).json(user)
})

router.put('/users/:userId', (req, res) => {
  const user = getUser(req.params.userId)

  if (!user) return res.status(404).json({})

  user.name = req.body.name
  res.json(user)
})

router.delete('/users/:userId', (req, res) => {
  const userIndex = getUserIndex(req.params.userId)

  if (userIndex === -1) return res.status(404).json({})

  users.splice(userIndex, 1)
  res.json(users)
})

const getUser = (userId) => users.find(u => u.id === parseInt(userId))
const getUserIndex = (userId) => users.findIndex(u => u.id === parseInt(userId))

// Ephemeral in-memory data store
const users = [{
  id: 1,
  name: 'Joe'
}, {
  id: 2,
  name: 'Jane'
}]
let userIdCounter = users.length

// The aws-serverless-express library creates a server and listens on a Unix
// Domain Socket for you, so you can remove the usual call to app.listen.
// app.listen(3000)
app.use('/', router)

// Export your express server so you can import it in the lambda function.
module.exports = app

というわけで、npmを追加します。

ターミナル
npm install express-jwt jwks-rsa --save

ちゃんとnode_moduleフォルダに追加されます。

access_tokenの検証プロセスを追加

上で準備したモジュールを読み込んで、tokenの検証プロセスを追加してみます。
issuerの末尾は/が入るので注意してください。

app.js(default+access_token_validation)
'use strict'
const path = require('path')
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const compression = require('compression')
const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware')
const app = express()
const router = express.Router()

const jwt = require('express-jwt');
const jwksRsa = require('jwks-rsa');

app.set('view engine', 'pug')

if (process.env.NODE_ENV === 'test') {
  // NOTE: aws-serverless-express uses this app for its integration tests
  // and only applies compression to the /sam endpoint during testing.
  router.use('/sam', compression())
} else {
  router.use(compression())
}

router.use(cors())
router.use(bodyParser.json())
router.use(bodyParser.urlencoded({ extended: true }))
router.use(awsServerlessExpressMiddleware.eventContext())

// NOTE: tests can't find the views directory without this
app.set('views', path.join(__dirname, 'views'))

router.get('/', (req, res) => {
  res.render('index', {
    apiUrl: req.apiGateway ? `https://${req.apiGateway.event.headers.Host}/${req.apiGateway.event.requestContext.stage}` : 'http://localhost:3000'
  })
})

// Create middleware for checking the JWT
const checkJwt = jwt({
  // Dynamically provide a signing key based on the kid in the header and the singing keys provided by the JWKS endpoint.
  secret: jwksRsa.expressJwtSecret({
    cache: true,
    rateLimit: true,
    jwksRequestsPerMinute: 5,
    jwksUri: `https://<YOUR_AUTH0_DOMAIN>/.well-known/jwks.json`
  }),

  // Validate the audience and the issuer.
  audience: 'YOUR_AUDIENCE',
  issuer: `https://<YOUR_AUTH0_DOMAIN>/`,
  algorithms: ['RS256']
});

router.get('/callback', (req, res) => {
  res.send('Please Login')
})

router.post('/callback', (req, res) => {

  //router.use(checkJwt)

  const all_params = {
    access_token: req.body.access_token,
    id_token: req.body.id_token,
    expires_in: req.body.expires_in,
    scope: req.body.scope,
    state: req.body.state,
    token_type: req.body.token_type
  }
  res.json(all_params)
})

// The aws-serverless-express library creates a server and listens on a Unix
// Domain Socket for you, so you can remove the usual call to app.listen.
// app.listen(3000)
app.use('/', router)

// Export your express server so you can import it in the lambda function.
module.exports = app

何をしたかというと、

const jwt = require('express-jwt');
const jwksRsa = require('jwks-rsa');

まず、ここで追加したモジュールを読み込みました。
次にサンプルにならって、access_tokenを検証するmiddlewareを構成しました。

// Create middleware for checking the JWT
const checkJwt = jwt({
  // Dynamically provide a signing key based on the kid in the header and the singing keys provided by the JWKS endpoint.
  secret: jwksRsa.expressJwtSecret({
    cache: true,
    rateLimit: true,
    jwksRequestsPerMinute: 5,
    jwksUri: `https://<YOUR_AUTH0_DOMAIN>/.well-known/jwks.json`
  }),

  // Validate the audience and the issuer.
  audience: 'YOUR_AUDIENCE',
  issuer: `https://<YOUR_AUTH0_DOMAIN>/`,
  algorithms: ['RS256']
});

そこから下に作られていた、デフォルトのGET/POSTを参考にしながら(と言いつつ消してしまいましたが)、callbackのGET/POSTを書いています。

router.get('/callback', (req, res) => {
  res.send('Please Login') //トークンなしでアクセスしてきたら、「Please Login」という文字列を返す。
})

router.post('/callback', (req, res) => {

  //router.use(checkJwt) //試しにcheckJwtを使わないで、何がPOSTされているか確認する。

  const all_params = {
    access_token: req.body.access_token,
    id_token: req.body.id_token,
    expires_in: req.body.expires_in,
    scope: req.body.scope,
    state: req.body.state,
    token_type: req.body.token_type
  }
  res.json(all_params)
})

この状態でシステムを更新して(npm run package-deploy)ログインをしてみていただくとわかりますが、ログインが終わると

  • access_token(アクセスを許可するトークン)
  • id_token(IdPから提供されたidの情報)
  • expires_in(アクセストークンの有効期限)

といった情報が返ってきています。access_token,id_tokenはJSON Web Token(JWT)になっているので、
https://jwt.io/
こちらのツールを使うと、内容を確認することができます。

callback.pugを適用する

それでは、最後に先ほど作ったcallback.pugを適用しましょう。
デフォルトのapp.jsには、こんな記述がありました。

app.js(default)
router.get('/', (req, res) => {
  res.render('index', {
    apiUrl: req.apiGateway ? `https://${req.apiGateway.event.headers.Host}/${req.apiGateway.event.requestContext.stage}` : 'http://localhost:3000'
  })
})

これがindex.pugを読んで、apiUrlにレンダリングしている部分のようです。
そこで、app.jscallbackPOSTを次のように書き換えてみます。

app.js(new)
router.post('/callback', (req, res) => {

  router.use(checkJwt)

  res.render('callback', {
    apiUrl: req.apiGateway ? `https://${req.apiGateway.event.headers.Host}/${req.apiGateway.event.requestContext.stage}/callback` : 'http://localhost:3000/callback'
  })
})

checkJwtが通ったら、callback.pug/callbackのURLにレンダリングしなさい、という形になったと思います。

動作確認

更新 --> 動作確認してみましょう。
ログインを行うと、こんなページが出てきます。
callback
「StartGame」のボタンを押してみます。
jscheck
jsが働いて、「hello world!」が表示されました!
デベロッパーツールが入ってしまってスクショが見苦しいのはご容赦ください。

独自ドメインの適用

API Gatewayとのやりとりは、httpsになりますが、独自ドメインを適用する場合はhttpで書いてきてしまう人もいるかもしれません。
そこで、httpsへのリダイレクトも同時にできてしまうCloudFrontを利用する方法で、独自ドメインを当てました。
流れは、こちらの記事に従います。
「CloudFrontに複数オリジン(API GatewayオリジンとS3オリジン)の設定」

Route53 --> CloudFront --> API Gatewayという流れになりますが、ドメインはUnityの埋め込みブラウザから見るぶんには気にならない部分ですので、今回は割愛します。

実際にやってみて面白かったのは、CloudFrontでrootに/prodとステージを入れておくと、実際にブラウザでアクセスするURLではステージを書く必要がなくなるんですね。

index.pug内のcallbackのURLなども書き換える必要がありますので、独自ドメインを当てる方はご注意ください。

また、上記の参考記事で「複数オリジン」の記事を選んでいる理由は、ウィジェットに使っているロゴマークをS3に置いていたからです。
どうせなら、ログイン周りの小物パーツは同じドメイン下に置きたいですよね。

SNSなどOpenIDでのログインを追加

さて、ここからがAuth0の真骨頂(?)ともいえる、SNSなどIdPとの簡単連携です。
そもそも、Lockでログインウィジェットを作ったとき、コード上ではなんの指定もしていないのに、Googleアカウントでのログインボタンが出ていましたよね。
このウィジェットが素晴らしいのは、Auth0のコンソールでポチポチ設定すれば、IdPが簡単に増やせることです。
実際にやってみましょう。

Auth0でDashboard > Connections > Socialを選択します。
social
すると、こんなふうに数多くのIdPから、ログインなどで連携したいサービスを選ぶことができます。
この記事はUnityで遊ぼう、といった趣旨なので有名どころのSNSと連携できればよいですが、ECサイトならamazonやpaypal、ビジネス系アプリならLinkedinなどと連携させてもいいでしょう。
ホント便利ですね。

今回はこちらのドキュメントを元に、GoogleとFacebookを追加してみます。
Identity Providers Supported by Auth0

Google

ドキュメント通りですが、https://console.developers.google.com/projectselector/apis/credentialsにアクセスすると、こんな画面になります。
google

「作成」で新しい認証情報を作ります。
私の場合は、会社がG Suiteを使っていることもあり、組織が会社に結び付けられました。

認証情報を作成 > OAuthクライアントIDを選びます。
google2

すると、ドキュメントとは違って、先に同意画面を作りなさい、と言われます。
hmm

そのまま、同意画面を設定することにします。
hmmm
ここでscopeを増やしておくと、Googleから提供される個人情報の選択肢の幅が増えます。
会員ビジネスなどでソーシャルログインが重宝されるのは、こうやってマーケティングのヒントになる情報が取れるからですよね。
ただ、今回はお試しなので、そのままにしておきます。

hmmmm
ここからはドキュメント通り、ウェブアプリ > 適当な名前 > auth0のドメイン > <auth0のドメイン>/login/callbackと入力しておきます。

すると、クライアントIDクライアントシークレットが生成されました。
client_id
これをコピーして、どこかに保存しておきます。

Auth0のコンソールに戻って、ソーシャルログインの一覧からGoogleを選ぶと、次のような画面になります。
setupsocial
このClient IDClient Secretに先ほど取得した値を入れます。

Permissionsは今回の場合、デフォルトのままで問題ありません。上部のタブのApplicationsでは、このテナント全体でこのIdPの設定を使うか、個別のApplicationで使うように限定するかを選ぶことができます。
個人的には、必要なものだけに制限したほうがよいだろうと思うので、そのようにしておきました。

下部のボタンでSAVEしたのち、TRYを押してみると、自分のGoogleアカウントでログインするかどうかを確認するモーダルが開きます。

ここでは、いったんこのページは無視して閉じておきましょう。
試しに、この記事で作ったログインページ、
https://<YOUR_API_PREFIX>.execute-api.ap-northeast-1.amazonaws.com/prod(または独自ドメインの方はそちら)
を開きます。
タブを開きっぱなしで作業していた方はリロードしてください。

「ユーザー登録」のタブで「G」のマークを押すと、先ほどのテストと同じように、ご自分のGoogleアカウントでログインしますか、と問われます。
承認すると、ログインが完了し、callbackに飛ばされます。
注意書きの文言が「auth0.com」と共有します、となってしまう部分をどうやったら解消できるのかは、ちょっと今後の課題です。
事実としてAuth0にも情報が共有されるわけなので、そこのところはしょうがないのか、何か書き方があるのかもしれません。

Facebook

同様にFacebookやTwitterもドキュメントにそって設定していけば、ウィジェットに勝手にボタンが追加されます。
Facebook側もDeveloperの様子がAuth0のドキュメントの作成時と変わっているところがあるようですが、コンソールのレイアウトが変わったというくらいです。
フローとしてはGoogleと同じで、privacy policyのURLなどを入力 > Client IDとSecretを取得 > Auth0に入力してSAVE > Applicationsで使うAppを選択となります。
この登録がAuth0のコンソールでうまく行くと、ウィジェットの様子が自動的に変わります(ブラウザをリロードしてください)。
addedFB

佳き哉。
いったん設定できてしまえば、Auth0のコンソールをポチポチするだけでウィジェットが反応してくれるというのがうれしいですよね!

Unityからアクセスしてみる

ここまでのログインのフローをwebベースで頑張ってきた理由は、まさに上で見たようなソーシャルでのログインのフローの特徴によるところが大きかったです。
というのも、アカウント連携の同意を取るために、やたらとリダイレクトされるんですね。
Unityのアプリ側でこういうフローを追っていくのは、かなり大変だと思います(実は大変じゃないのかもしれないけど、素人目には大変そう)。

ここからは、いよいよUnityでこのログインページにアクセスしてみます。

まず、index.pugcallback.pugのstyleを調整しておきます。
ここまでは一般的なブラウザで見たときに心地よい感じにしてありましたが、UnityのUI内に埋め込むブラウザは小さいので、上下のmarginを40px取っていたところを0にしておきましょう。

div#root {
  margin: auto;
  padding: 0px;
  box-sizing: border-box;
  }
div#button-area {
  width: 320px;
  margin: auto;
  padding: 0px;
  box-sizing: border-box;
  }

修正するファイルは別々ですが、それぞれ上記のようなstyleにしておきます。
lockのデフォルトのCSSは、横幅481pxをブレークポイントとするレスポンシブなので、埋め込みブラウザの横幅は482pxとしましょう。

UnityのEmbedded Browser

Unityで使えるブラウザはいくつかあるようですが、個人的にいいと思ったのは、
https://assetstore.unity.com/packages/tools/gui/embedded-browser-55459
こちらです。ChromeのコアであるChromiumベースで作られていて、実際の使用感もいい感じです。
ただ、弱点もあります。以下ではPCゲームっぽい想定で話を進めていますが、embedded browser自体がPC向けで、Android/iOSに対応していません。
(macOS buildに対応しているのは、逆にembedded browserくらいだ、というコメントも見受けられましたが…)

スマホ向けを含めて検討されている方は、他の選択肢も探されてみてください。
「UNITYでのWEBVIEW(埋め込みブラウザ)について」
「【Webview】unity-webview / UniWebView3」

さて、そのembedded browserですが、
Unity > AssetStore > "embedded browser"で検索
という流れで購入、importしてください。

Embedded BrowserにはいくつかのPrefabが含まれていますが、今回使うのはGUI用のPrefabです。
browserprefab

Hierarchy window右クリック > UI > Panelを行い、UI用のパネルを1枚作成します。
すると、同時にCanvasとEventSystemがついてきます(この辺りはお好みで。単にCanvasだけ作っても問題ありません)。
このCanvas > Panelの下に、GUI用のBrowser Prefabをドラッグ&ドロップしてください。
hierarchy

とりあえず右端のほうにログインフォームを持っていきましょう。hierarchyでBrowserオブジェクトを選択し、inspectorで下のように設定します。

Browser(Script)Urlには、これまでに構築してきたログインウィジェットのURLを入れてください。

ここでゲームを実行してみます。すると…
ingame
どーん。
はい、ログインウィジェットが出ました!

試しに、mail/passwordでログインしてみます。その結果がこちら。
gamestart
うーん、位置がいまいちですが、「StartGame」ボタンが出ました!
押してみましょう。
jsworked
ちゃんとhello world!が表示されました!

ちなみに、他のIdPを選ぶとこんな風にリダイレクトされます。
Google
これこれ、こういうリダイレクトがブラウザベースなら簡単に取り回せるので、Unityに埋め込んだときにもうれしいですよね。

Panelにゲームのランチャー風の画像を置いてやれば、かなり雰囲気が出てくるのではないでしょうか。
launcher

シーン切り替えに挑戦

シーンの切り替えも、画面やBGMのFadeout/Fadeinなど、本来ならいくつか気をつかいたいポイントはあります。
また、実際にランチャーモードでログイン > ゲーム本編は全画面といった作りもあり得るので、windowの制御も入れたいかもしれません。
ですが、今回は話をシンプルにして、単純にシーンのスイッチ指示がjavascript経由で出せればOKとしてやってみます。

embedded browserで読み込んだjavascriptとunity本体のC#の連携については、こちらの記事などで解説されているのを見つけました。
「Unity・VR内でブラウザを利用するメモ」
「Embedded Browserの公式ドキュメント」

先に、シーン転換のトリガーとなるjsのほうを書いてしまいます。
ここではonclickで指定されたイベントを取っているだけなので、実際の中身はconsole.logにしていますが、引数をUnity側に渡すこともできます。

callback.pug
html
  head
    meta(name='viewport', content='width=device-width, initial-scale=1')
    meta(http-equiv='Content-Type', content='text/html; charset=UTF-8')
    style.
      input {
        width: 16rem;
        height: 4.5rem;
        margin: 0 auto;
        background-color: #33cccc;
        color: #fff;
        font-size: 20px;
      }
      div#button-area {
      width: 320px;
      margin: auto;
      padding: 0px;
      box-sizing: border-box;
      }
    script(type='text/javascript').
      function startGame(){
      console.log('clicked!');
      }
  body
    div#button-area
      input(type='button', value='StartGame', onclick="startGame()")

listener側は、browserオブジェクトにStartGameListener.csをアタッチしておきます。

StartGameListener.cs
using UnityEngine;
using UnityEngine.SceneManagement;
using ZenFulcrum.EmbeddedBrowser;

[RequireComponent(typeof(Browser))]
public class StartGameListener : MonoBehaviour
{
    private Browser browser;

    // Start is called before the first frame update
    void Start()
    {
        browser = GetComponent<Browser>();
    }

    // Update is called once per frame
    void Update()
    {
        browser = GetComponent<Browser>();
        browser.RegisterFunction("startGame", arg => GameStarter());
    }

    private void GameStarter() 
    {
        browser = GetComponent<Browser>();
        browser.gameObject.SetActive(false);
        Destroy(browser.gameObject);
        SceneManager.LoadScene("Scene2", LoadSceneMode.Single);
    }
}

何もこんなにしつこくbrowserオブジェクトを消そうとしなくてもいいと思うのですが、なんとなく放っておくと無駄にメモリーを食うんじゃないか(というイメージ)があったので、消してみました。
実際にシーン移動をしてみるとわかりますが、次のシーンにいっても「DontDestroyOnLoad」というオブジェクトが残ります。
これはeditorのplaymodeなどの終了時に、embedded browserを確実に終了させるためのオマケだそうですので、神経質に消そうとしなくてもよさそうです。

「Scene2」を作って動作確認

Scene2を作ったら、build settingsでちゃんとbuild対象にしておくようにしましょう。
ここでは、イラスト1枚ですが、こんなシーンにしてみました。
Scene2

いざ、実際に最初のシーンから動かしてみます。
result

一部にモザイクをかけた上にgif化したら、画質がえらいことになっていますが、ログインして、StartGameを押すと、次のシーンに行けるようになったことが確認できると思います。
やったね!

本当はやるべきこと

今回の記事で欠けているのは、

  • ログアウトする機能
  • Auth0のAPIの権限調整(APIのscopeを制限すること/Rulesの設定など)
  • access_tokenのexpire_inの適切な設定を考えること(どれくらい生かしておくか?)
  • tokenの取り回しが本当にセキュアにできているか(というか余計なところで気づかぬうちにストアされたりしていないか)、ちゃんとチェックしたかった
  • 欲を言えば、S3へのアクセスなどAWSリソースの権限(IAM)を取り回すところまでやりたかった

などなどですが、とくにログアウトの仕方を考えていて、Auth0のこちらのドキュメントを見つけました。
「Node.js」
こっちをベースにログインその他も作ったほうが早くてスマートだったんじゃないかと思ったり…。
また実験してみます。

ひとまず、Qiita初投稿は、これにて終了です!
ご静聴ありがとうございましたm(_ _;)m