Vue.jsアプリにAuth0とGraphQLを使って認証と承認を追加
https://blog.hasura.ioの元の記事のリンク
TL;DR (この記事の概要)
- Auth0でVue.jsアプリに認証を追加
- Hasura GraphQLのpermissionを使ったJWT認証
- ログインしたユーザーが書いた記事だけが読めるサンプルVueアプリ
- サンプルアプリのソースコード
技術スタック
アプリは次のスタックを使用して、動作させるために以下の設定および構成に必要があります。
- vue-cli-plugin-apolloとvue-routerを使ったVue.js
- Auth0で認証
- Hasura GraphQLエンジンでInstant GraphQL API
GraphQL APIを準備するために、Hasuraをpostgresと一緒にデプロイしましょう。
Hasuraのデプロイ
Hasuraは、新規または既存のPostgresデータベース上でリアルタイムのGraphQL APIを提供するオープンソースのエンジンです。カスタムGraphQL APIのステッチおよびデータベース変更時のWebフックのトリガーをサポートしています。
Hasuraをデプロイはドキュメントに従ってください。 Heroku URLがGraphQLエンドポイントになります。後でこれをアプリに設定します。
このセクションの手順に従って移行を適用し、必要なデータベーススキーマと権限を作成します。
これでバックエンドの準備が整いました! Hasura GraphQL APIを使用して即座にクエリを実行できるようになります。エンドポイントは(https://myapp.herokuapp.com/v1alpha1/graphql)のようになります。 Vueアプリの開発中にこれを使います。
Auth0でアプリを作成
- Auth0ダッシュボードに進み、Single Page Web Appタイプのアプリケーションを作成します。
2. アプリケーションの設定で、 “Allowed Callback URLs”としてhttp:// localhost:3000/callback、 “Allowed Web Origins”としてhttp://localhost:3000を追加して、アプリのローカル開発を有効にします。
カスタムJWTクレームのルールの追加
Auth0ダッシュボードで、[Rules]に移動します。以下のルールを追加して、カスタムJWTクレームを追加します。
function (user, context, callback) {
const namespace = "https://hasura.io/jwt/claims";
context.idToken[namespace] =
{
'x-hasura-default-role': 'user',
// do some custom logic to decide allowed roles
'x-hasura-allowed-roles': user.email === 'admin@foobar.com' ? ['user', 'admin'] : ['user'],
'x-hasura-user-id': user.user_id
};
callback(null, user, context);
}
JWT証明書の取得
https://hasura.io/jwt-configにアクセスして、Auth0ドメイン用の設定を生成します。
認証アプリケーション用に生成されたJWT Configをコピーします。
HasuraでJWTモードを有効に
上記で生成された設定は、Hasura_GRAPHQL_JWT_SECRET
環境変数で使用される必要があります。 JWTモードを有効にするには、HASURA_GRAPHQL_ADMIN_SECRET
キーも設定する必要があります。
これを追加すると、GraphQLエンドポイントはAuthorization
ヘッダーまたはX-Hasura-Admin-Secret
ヘッダーがある時だけクエリできます。
Auth0ルールの作成
ユーザーがAuth0にサインアップするたびに、そのユーザーをpostgresデータベースに同期させる必要があります。これはAuth0ルールを使って行われます。別のルールを作成して次のコードを挿入します。
function (user, context, callback) {
const userId = user.user_id;
const nickname = user.nickname;
request.post({
headers: {'content-type' : 'application/json', 'x-hasura-admin-secret': '<your-admin-secret>'},
url: 'http://myapp.herokuapp.com/v1alpha1/graphql',
body: `{\"query\":\"mutation($userId: String!, $nickname: String) {\\n insert_users(\\n objects: [{ auth0_id: $userId, name: $nickname }]\\n on_conflict: {\\n constraint: users_pkey\\n update_columns: [last_seen, name]\\n }\\n ) {\\n affected_rows\\n }\\n }\",\"variables\":{\"userId\":\"${userId}\",\"nickname\":\"${nickname}\"}}`
}, function(error, response, body){
console.log(body);
callback(null, user, context);
});
}
admin secret
とURL
を自分の値で置き換えます。
これで全てのバックエンドと認証設定の準備が整いました。正しいヘッダを使用してGraphQLクエリを作成するようにVue.jsフロントエンドを設定しましょう。
Vue-CLI-Apollo-Pluginの設定
boilerplateコードを使って始めるために、Auth0のサンプルアプリケーションを使用します。
次のコマンドでVueアプリ用のアポロクライアントセットアップを生成します。
vue add apollo
これにより、src
にvue-apollo.js
というファイルが生成されます。このファイルでは、options
オブジェクトgetAuth
を次のように定義して構成します。
getAuth: tokenName => {
// get the authentication token from local storage if it exists
// return the headers to the context so httpLink can read them
const token = localStorage.getItem('apollo-token')
if (token) {
return 'Bearer ' + token
} else {
return ''
}
},
この設定は、ApolloClientが、クエリまたはサブスクリプションを作成するときに、Auth0によって返されたトークンをAuthorization
ヘッダーに使用するようにします。
認証されたクエリ
Apollo Clientは上記の設定で正しいheaderが設定されています。それでは、ログインしているユーザーによって書かれた記事のリストを取得するための簡単なクエリを追加しましょう。
export default {
apollo: {
// Simple query that will update the 'article' vue property
article: gql`query {
article {
id
title
}
}`,
},
}
これで、ユーザーがアプリにログインしている場合にのみ表示したいと思います。
Home.vue
の<template>
タグでは、記事を一覧表示するために次のコードスニペットを使用します。
<template>
...
...
<div v-if="isAuthenticated">
<h1 class="mb-4">
Articles written by me
</h1>
<div v-for="a in article" :key="a.id">
{{a.id}}. {{ a.title }}
</div>
</div>
...
...
</template>
このマークアップは、isAuthenticated
がtrueを返す場合にのみレンダリングされる必要があることを確認していることに注意してください。これを実装するために、ログインが成功するたびにイベントを発行します。
src/auth/authService.js
にアクセスして、Auth0ログインとイベント発行の実装の詳細を確認してください。
このファイルでは、ログインが成功するとイベントが発行されます。
this.emit(loginEvent, {
loggedIn: true,
profile: authResult.idTokenPayload,
state: authResult.appState || {}
});
src/plugins
でこのイベントを処理するためのplugin
が登録されました。
import authService from "../auth/authService";
export default {
install(Vue) {
Vue.prototype.$auth = authService;
Vue.mixin({
created() {
if (this.handleLoginEvent) {
authService.addListener("loginEvent", this.handleLoginEvent);
}
},
destroyed() {
if (this.handleLoginEvent) {
authService.removeListener("loginEvent", this.handleLoginEvent);
}
}
});
}
};
そのためloginEvent
が発生すると、handleLoginEvent
メソッドが呼び出されます。
そして、Home.vue
コンポーネントでは、そのメソッドを処理してisAuthenticated
値を更新します。デフォルトではfalse
であり、ログインが成功するとtrue
に更新されます。
methods: {
handleLoginEvent(data) {
this.isAuthenticated = data.loggedIn;
this.isLoading = false;
}
},
上記のGraphQLクエリは、Auth0から返されたトークンヘッダを使用して送信され、これが認証を処理します。
Authorization using JWT
ユーザーはログインしていますが、同じユーザーによって書かれた記事のみを表示したいです。記事を書いたユーザだけがデータを取得できるようにパーミッションが設定されています。
HerokuアプリのURLに移動してHasuraコンソールを開き、Data->article->Permissionsに移動して、user
ロールに定義されている権限を確認します。
権限チェックは次のようになります:
{ "user_id": {"_eq": "X-Hasura-User-Id"}}
これは、リクエストがクライアントからAuthorization:Bearer <token>
で送信されている場合、トークンペイロードからX-Hasura-User-Id
値を探し、user_id
列をフィルタリングして、ログインしていることだけを確認することを意味します。ユーザーはデータを取得し、自分のデータのみを取得します。ユーザーはすべての列にアクセスする権限を持っています。
Vue Routerを使ってルーティングの保護
Vue Routerを使用しているので、Global Before Guard
を使用してナビゲーNavigation Guards
を追加できます。これは、ナビゲーションがトリガされ、ナビゲーションが解決されるまで保留中と見なされるたびに呼び出されます。
src/router.js
では、解決する前にブール値のauth.isAuthenticated()
をチェックするbeforeEach
ガードを定義しています。
router.beforeEach((to, from, next) => {
if (to.path === "/" || to.path === "/callback" || auth.isAuthenticated()){
return next();
}
auth.login({ target: to.path });
});
この場合、ページが/
や/callback
ではない、またはユーザーが認証されていない場合、ユーザーはauth.login
メソッドを使用してログインページにリダイレクトされます。
アプリの実行
Vue.jsアプリでHasura GraphQLエンドポイントを設定する必要があります。 src/vue-apollo.js
に移動して、httpEndpoint
およびwsEndpoint
の値を適切に変更してください。
次のコマンドを実行してサンプルアプリを実行します:
npm install
npm run serve
次のような画面が表示されるはずです:
あなたがすぐに始めることができるように、boilerplateをまとめてみました!
githubのコードも見ておいてくださいね。
そのコードを走らせてみて感想を聞かせてください。質問がある場合、または問題が発生した場合は、Twitter、github、またはdiscordサーバーでお気軽にお問い合わせください。