この記事はRetty Advent Calendar 2019 15日目です。 昨日は池田さんのドメイン知識を表すモデルでアジャイルな開発を支えるでした。
はじめに
こんにちは!エンジニアリング部門の諏訪です。 みなさん普段フロントエンドとのapiのインターフェースには何を使っていますか? 弊社ではマイクロサービス化を進めており、フロントエンドとのやりとりに一部GraphQLを採用していたりします。
ただデータ返すだけなら楽ですが、認証認可したくなることってよくありますよね。 今回検証のため、Auth0を使ってユーザー認証とGraphQLのリソースごとの認可の仕組みを作ってみようと思います。
今回フロントエンドではユーザーを特定するための認証を行い、バックエンドでtokenのバリデーションとリソースの認可をします。
※ RettyのサービスにはAuth0は採用しておりません。
検証に使った主なものは以下の通りです
- Auth0
- Go 1.13.0
- go-chi
- gqlgen
- Vue.js
- vue-apollo
早速作ってみる
Auth0準備
早速Auth0でプロジェクトを作ってみます。
DashboardのApplicationsタブから CREATE APPLICATION
で作成できます。
作成できるとこんな感じでフレームワークごとのドキュメントが用意されておりこれに従うだけで簡単に導入することができます。
今回の検証ではユーザー登録の部分までは作成しないのでDashboardのUsers & Rolesタブからこのアプリケーションに使うユーザーをあらかじめ登録しています。
ここまでで認証をする準備は整いました。Auth0簡単ですね。今回は認可の仕組みを作りたいのでさらに踏み込んで認可の準備をします。
DashboardのExtensionsタブから Auth0 Authorization
を有効にします。
Authorization ExtensionのDashboardに入れるようになるので Roles
タブからRoleを作成しあらかじめユーザーに紐づけておきます。ここでもロール作成・追加の部分までは作成しないので手で作っておきます。
上のロールをidTokenに載せるためにRuleを作成し注入します。
function (user, context, callback) { context.idToken.test_auth_app_authorization = user.app_metadata.authorization; callback(null, user, context); }
これでAuth0の準備が整いました。
Vue.jsへ導入
フロントエンドではユーザー認証のみ行いリソースへアクセスできるかのチェックは行いません。id tokenをパースすればどのロールを持っているかの判別はできるため、フロントでのコンテンツの出し分けとかも実装できます。 Vue.jsの環境構築とAuth0の導入はVue CLIとAuth0のQuick Startをみると簡単なので詳しい導入の仕方は割愛しますが、いくつかポイントを紹介します。(検証に使ったコードは記事の最後に記載いたします)
Vuex と vuex-persistedstate を使って画面遷移ごとにAuth0へ問い合わせしないようにする
画面遷移のたびに認証しているとページの表示に時間がかかってしまうのでStoreを使ってlocalStorageにキャッシュします。
Vue RouterのNavigation Guardsでルートごとに認証チェックをする
ルーティング後にViewコンポーネントごとに認証チェックをすると、Viewコンポーネントの数だけ認証チェックのロジックを入れないと行けなくなってしまうのでVue RouterのNavigation Guardsで認証のチェックをし、リダイレクトする処理を挟んでいます。認証の必要のないエラーページや認証通る前のcallback等はauth処理を挟みたくないのでmetaフィールド使ってルーティングごとに認証スキップできる仕組みを入れてます。
Navigation Guards
Route Config
GoでGraphQLサーバーを作る
フロントエンドとバックエンドとのやりとりはAuthorizationヘッダーを使ってBearerトークンを投げMiddlewareを使って認証させます。
※ 今回は検証のための実装なのでセキュリティ的な検証はしてないので注意してください
認証してroleをcontextに注入するCustom Middlewareを作る
ヘッダーからAuthorizationヘッダーを抽出し、JWTの検証をします。 検証ができなければ400を返し、検証がOKであればid tokenからroleを抽出しcontextに注入してます。 Middlewareを作る際にjwkを更新するgoroutineを仕込むようにしてます。
Resolverでリソースごとのロールのチェックをする
ロールが含まれなかったらerrorを返す処理を入れます。 こうすることでロールが含まれるリソースにはデータが入り、ロールがなかった場合にはデータは入らずerrorが返る処理になります。
まとめ
ユーザーごとにリソースの認可ができるようになりました。今回は雑に検証しただけでセキュリティが担保されていることの検証 までは行なっていません。実際に認証認可の実装をする場合はセキュリティに十分気をつけてください!
今回使用したコードはこちらにあげております。