ReactとGoバックエンドの組み合わせでAuth0を使ってみた
はじめに 〜そもそもAuth0とは〜
株式会社バニッシュ・スタンダードのヒダです。弊社で提供している「STAFF START」というtoBのサービスでGoのバックエンドだったりiOSアプリの開発をやったりしています。
Auth0は雑にいうとユーザの登録やログイン、ユーザ情報の管理を代わりにやってくれるサービスです。Auth0を通してこれらを行うことで、自分たちのサービスを使っているユーザが誰なのか(=Identity、アイデンティティ)を識別できるようになります。
この記事では以下の2つのやりたいことについてざっくり実装する際の要点を説明します:
- やりたいこと1: ユーザを特定して保存
- やりたいこと2: Go API経由でAuth0のユーザ情報を取得
やりたいこと1: ユーザを特定して保存
やりたいことはReactのフロントエンドでユーザー登録やログインができて、かつログインしたユーザとひもづいた何らかの情報をGoのバックエンドAPIに送って保存できるようにすることです。
「何らかの情報」だとイメージしづらいかもしれないため、いったん好きなラーメン🍜を保存することにします。
処理の流れをざっくりいうとこんなイメージです:
- React上でAuth0のSDKを利用したログイン画面でログインする
- ログイン後、Auth0を使ってGoのAPI用のアクセストークンを取得する
- アクセストークンを使って好きなラーメン🍜を保存するためのGo APIを叩く
- Go APIでアクセストークンを検証する
- ユーザを特定してそのユーザが好きなラーメン🍜をデータベースに保存する
必要なこと
これらを実現するために必要なことは色々ありますが、この記事では以下を説明します。Reactのプロジェクトをどう構成するか、GoでDBにどうやってデータを保存するか、といったことは説明しません。
- Auth0のアカウント作成と設定
- アカウント作成
- 設定
- 必要な情報の取得
- Reactの実装
- ログイン画面の表示
- アクセストークンの取得
- Goの実装
- アクセストークンの検証
1. Auth0のアカウント作成と設定
アカウント作成
https://auth0.com/ からアカウント作成用のボタンを押して登録してください。登録するだけなら無料です。ウインドウの幅が狭いとメニューの中に隠れたりするので探してください。
ちなみに「I need advanced settings」みたいなチェックボックスは、チェックしておくとリージョンの設定ができ、日本のサーバを選ぶことができます(デフォルトだとアメリカになるはずです)。
ちなみにちゃんと公式ドキュメントを読みたい方はログイン後、React SPAとGoバックエンドそれぞれのQuickstartsをやっておくのがおすすめです。ログインしておくとサンプルコードの中の必要な設定の値が自分のアカウントのものに変わるのでそのまま試せますし、「Download Sample」からそれらの値が設定されたサンプルをダウンロードすることもできます。
- https://auth0.com/docs/quickstart/spa/react/interactive
- https://auth0.com/docs/quickstart/backend/golang/interactive
設定
Auth0のダッシュボードにログインして以下を行います:
- React SPAを「Applications」に追加
- Go APIを「APIs」に追加
ちなみに上で触れたQuickstartsにもこれらを新規作成するボタンがあり、それを押してQuickstartsを進めると大体同じ設定をすることになります。
React SPAを「Applications」に追加
「Applications」メニューの下にさらに「Applications」があるので、そこの「Create Application」から「Single Page Apps」「React」を選択します。
あとはQuickstartと同様、「Allowed Callback URLs」「Allowed Logout URLs」「Allowed Web Origins」にhttp://localhost:3000
(ローカルで立ち上げるReact開発サーバのURL)を入れていきます。
Go APIsを「APIs」に追加
「Applications」メニューの下には「APIs」というメニューもあります。GoのAPIはこちらに追加します。最初からある「Auth0 Management API」が気になりますが次のやりたいことで関係するのでいったん忘れてください💭
ところでなぜここに追加する必要があるのかというと、React側でログインやアクセストークンの取得をする際、ここで追加したAPIの「Identifier」の値を指定することで、このAPIのためのアクセストークンを取得できます。
という訳で「Create API」を押してAPI名と「Identifier」を入れます。とりあえずローカルのGoのURLなどにすればいいですが、実際は何でも構いません。例えば複数のサーバで動いている全てのAPIで同じIdentifierを使うこともできるので、その時はURLでない値を使うかもしれません。
必要な情報の取得
以上の作業が終わればローカルの開発に必要な情報がそろうので、ダッシュボードから取得します。
- React SPAに必要な情報
- Domain:
dev-xxxxxxxxxxxxxxxx.jp.auth0.com
- Client ID
- GoのAPIのIdentifier
- Domain:
- Go APIに必要な情報
- Domain: (React SPAで使うものと同じ)
- Identifier
2. Reactの実装
ログイン画面の表示
基本的にはQuickstartsの内容の通りですが、audienceとしてGoのAPIのIdentifierを指定します。ここでのaudienceはアクセストークンを消費(Consume)するものを表していて、今回はGoのAPIでこのアクセストークンを使うのでGo APIがaudienceになります。ここで指定した値はアクセストークンの中に埋め込まれ、API側でこのアクセストークンを検証する際に本当にこのAPIで使うことを意図されたアクセストークンなのかをチェックすることになります。
なおこの辺の値は実際には環境変数などから取得することになると思います。
<Auth0Provider
domain={[Domain]}
clientId={[Client ID]}
authorizationParams={{
redirect_uri: window.location.origin,
audience: [GoのAPIのIdentifier], // Quickstartsの内容にこれを追加
}}
>
// (略)
</Auth0Provider>
あとはloginWithRedirectを呼ぶとAuth0が用意したログイン画面が表示されます。以下はQuickstartsのログインボタンの実装例です。
const LoginButton = () => {
const { loginWithRedirect } = useAuth0();
return <button onClick={() => loginWithRedirect()}>ログイン</button>;
};
アクセストークンの取得
auth0-reactのgetAccessTokenSilently
でGo APIのアクセストークンが取得できます。
const accessToken = await getAccessTokenSilently({
authorizationParams: {
audience: [GoのAPIのIdentifier],
},
});
取得できたらGo APIにリクエストする際、ヘッダにアクセストークンを含めます。
const headers = new Headers();
headers.set("Authorization", `Bearer ${accessToken}`);
3. Goの実装
アクセストークンの検証
React側からアクセストークンがJWT(JSON Web Token)の形式で送られるので、デコード&検証してさらにAuth0上のユーザーIDを取得します。
Quickstartsのmiddleware/jwt.goのfunc EnsureValidToken()
の内容が参考になります。
ここではjwtmiddlewareを利用してMiddlewareの形にして使っています。HTTPヘッダからトークンを取り出すところはこのMiddlewareが勝手にやってくれます。
自分でヘッダからアクセストークンを取り出した場合は、jwtValidator.ValidateTokenに直接それを渡して検証することもできます。
validToken, err := jwtValidator.ValidateToken(ctx, token)
さらに以下のようにすればJWTのSubjectからAuth0のユーザーIDが取得できます。Auth0のデフォルトのデータベースにメールアドレス/パスワードで登録したアカウントの場合は auth0|xxxxxxxxxxxxxxxx
のような文字列になります。今回は取り上げませんが、Gmailでのログインだと google-oauth2|xxxxxxxxxxxxxxxx
のようになります。
claims := validToken.(*validator.ValidatedClaims)
userID := claims.RegisteredClaims.Subject
このユーザーIDと自分のシステム上のユーザのひもづけの情報を作成すれば、Auth0でログインしたユーザ固有の情報を扱うことができます。保存するものは好きな🍜だろうと🍶だろうと何でもいいです。
やりたいこと2: Go API経由でAuth0のユーザ情報を取得
もう1つ、Auth0上のユーザの情報を扱うときの方法を説明します。今回は情報の取得にしぼりますが、例えばユーザの削除や別アカウントとの連携を設定する時も利用するAuth0のAPIが変わるだけで流れは大体同じです。
必要なこと
Auth0にはユーザーなどを管理するためのAPIとして「Management API」というAPIが用意されています。基本的にはAuth0のダッシュボードでできることは大体このAPIでできるようです。
このAPIはcurlなど直接HTTPリクエストを送って呼びだすこともできますし、各言語向けのSDKを通して利用することもできます。今回はGoなのでgo-auth0を使います。
やりたいこと1をやっている前提で、追加で必要なのは以下です:
- Auth0側の設定
- M2Mアプリケーションの作成
- 必要な情報の取得
- Reactの実装
- やりたいこと1と同様にアクセストークンでGo APIを叩けばいいので省略
- Goの実装
- ユーザ情報の取得
1. Auth0側の設定
M2Mアプリケーションの作成
設定の前に「Machine To Machine Application(M2Mアプリケーション)」について説明します。やりたいこと1で追加したReact SPAのアプリケーションとは別の用途のアプリケーションで、名前の通りマシン対マシンで使います。ちょっとまわりくどいかもしれませんが、今回GoのAPIからManagement APIを使う際、まずM2Mアプリケーションを作成して、GoからはこのM2MアプリケーションとしてManagement APIにアクセスします。
作り方は前回Reactのアプリケーションを作った時同様「Create Application」を押してから「Machine to Machine」を選び、APIのセレクトボックスで「Auth0 Management API」を選びます。
すると許可するパーミッションのリストが表示されるので、「read:users」にチェックを入れて許可します。こうすることでこのM2Mアプリケーションからはユーザの取得ができるようになります。ユーザ情報の更新が必要なら「update:users」といったように、必要に応じて許可します。
必要な情報の取得
M2Mアプリケーションが作成できたら、その詳細の「Settings」で以下を取得します。
- Go APIに追加で必要な情報
- Client ID
- Client Secret
2. Reactの実装
やりたいこと1と同様、getAccessTokenSilentlyなどでアクセストークンを取得し、それを使ってこの次で作成するGo APIを叩けばいいだけなので省略します。
3. Goの実装
go-auth0を追加したら、以下のようにしてManagement APIにアクセスするためのクライアントを生成できます。
client, err := management.New(
[Domain],
management.WithClientCredentials(
ctx,
[Client ID],
[Client Secret],
),
)
ユーザ情報の取得
あとはアクセストークンのJWTをデコードしてとりだしたユーザーIDを引数に渡して、以下のように呼び出せばAuth0のユーザー情報を取得できます。
user, err := client.User.Read(ctx, [userID])
ちなみにclient.UserのUserはなんらかのユーザーの情報が入っている訳ではなく、ネームスペース的なもので、型としてはUserManagerの値が入っています。例えばclient.Log以下のメソッドからはAuth0の利用ログ(ユーザーがいつログインしたか等)が取得できます。
このメソッドは実際は以下のエンドポイントを叩いているだけなので、レスポンスの内容等はここを見れば分かります。Goで使っていても基本はWebのAPIなので、エラー情報の中に400や404のようなステータスコードを持っていたりします。
おわりに
Auth0はQuickstartsやドキュメントが充実しているので基本はそれを読めばとりあえず使えるようになりますが、今回のようにReact+Goなど、組み合わせた場合の説明はそんなに多くなさそうだったので、Quickstartsを補完する形で書いてみました。
実際に実装してみる中で、アカウントリンク機能などそこそこハマりどころや制約があるものもぼちぼちあったので、その辺もいずれ記事にできればと思います。
Discussion