Auth0のログイン機能で取得したaccess_tokenを使ってRails側で認証を通してみた

こんにちは!kossyです!




さて、今回はAuth0のログイン機能で取得したaccess_tokenを使ってRails側で認証を通してみたので、
備忘録としてブログに残してみたいと思います。



環境

Rails

Ruby 2.6.3
Rails 6.0.3.4
Docker-Desktop

Vue

@vue/cli 4.5.9
vue@3.0.5
npm 6.14.8
node 14.15.0




前提

下記拙著内で作成した認証情報を用いることを前提に話を進めます。

kossy-web-engineer.hatenablog.com


Rails側での準備

以下のAuth0の中の人が書いたチュートリアルを参考にRailsのプロジェクトを作成しました。
auth0.com

ブログに書いてないもので別途対応したことは、CORSの対応です。

今回はVue.jsからRailsAPIへCROSS ORIGINな通信を行いたいので、rack-cors gemを導入します。

# Gemfile

get 'rack-cors'

# terminal

$ docker-compose run web bundle install

config/initializers/cors.rbを編集します。

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'
    resource '*',
    :headers => :any,
    :expose  => ['access-token', 'expiry', 'token-type', 'uid', 'client'],
    :methods => [:get, :post, :options, :delete, :put, :patch]
  end
end

今回はoriginsを * (どこのオリジンからの通信でも許可する)にしていますが、この場合だとSameOriginPolicyが意味を為さなくなってしまい、
XSSCSRFに対して脆弱になってしまうので、実際に運用するときは環境変数にオリジンを格納する等して、
ワイルドカードではなくきちんと指定してあげるようにして下さい。

参考: なんとなく CORS がわかる...はもう終わりにする。 - Qiita

また、chirps_controller.rbのskip_before_action :authorize_request, only: [:index, :show]もコメントアウトするようにしてください。

app/controllers/chirps_controller.rb

# skip_before_action :authorize_request, only: [:index, :show]

これでRails側の準備は完了です。


クライアント側の準備

以前執筆した拙著の中で作成したAuth0での認証機能が追加されたVue.jsアプリをクライアントとして使います。

kossy-web-engineer.hatenablog.com

src/router/index.tsに以下のコードを追記します。

// 省略
import { routeGuard } from '@/auth'

const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    name: 'Home',
    component: Home,
    beforeEnter: routeGuard // 追加
  },

// 省略

beforeEnterプロパティにrouteGuard関数を指定することで、ルートパスにアクセスがあった場合にコンポーネントの描画が行われる前に認証を要求されるようになります。



また、HTTP通信を行いたいため、今回はaxiosを導入します。

$ npm i axios

RailsAPIを叩くコードを追加します。

$ mkdir src/api

$ touch src/api/client.ts

$ touch src/api/chirp.ts

$ touch .env.development
// src/api/client.ts

import axios from 'axios'

export default axios.create({
  baseURL: process.env.VUE_APP_API_BASE
})
import Client from '@/api/client'

export const getChirps = async (token: string) => {
  return await Client.get('/chirps', { headers: { 'Content-Type': 'application/json', Authorization: `bearer ${token}` } })
    .then((response) => {
      return response.data
    })
}
// .env.development

VUE_APP_API_BASE=localhost:3000

client.tsはaxiosインスタンスをbaseURL付きで生成するコードになっています。
axiosを直接コールするのではなく、chirp.tsでimportしClient.get(...)のように呼び出せるようにします。

ライブラリをラップしてあげることで、仮にライブラリを差し替えることになっても、修正範囲を少なくすることができます。

chirp.tsはlocalhost:3000/chirpsを叩く関数を定義しています。
引数でtokenを受け取って、HTTPリクエストヘッダーに載せる形で用います。

.env.developmentはVue.jsで環境変数を扱う場合に用いるファイルです。
process.env.VUE_APP_API_BASEのように記述することで、ファイル内で環境変数を使うことができます。

次はgetChirps関数を叩くためにsrc/views/Home.vueを修正します。

// src/views/Home.vue

<template>
  <button @click="handleGetChirps">Get Chirps</button> // 追加
  <div v-if="!auth.loading.value">
    <button v-if="!auth.isAuthenticated.value" @click="handleLogin">Log in</button>
    <div v-if="auth.isAuthenticated.value">
      <button @click="handleLogout">Log out</button>
      <p>{{ auth.user.value.name }}</p>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent, inject, reactive } from 'vue'
import { getChirps } from '@/api/chirp'           // 追加

export default defineComponent({
  name: 'Home',
  setup () {
    const auth = inject<any>('$auth')

    const handleGetChirps = async () => {       // 追加
      const res = await auth.getTokenSilently() // 追加
      await getChirps(res).then((res) => {          // 追加
        console.log(res)                                         // 追加   
      })                                                                    // 追加
    }                                                                       // 追加

    return {
      auth,
      handleGetChirps, // 追加
      handleLogin: () => {
        auth.loginWithRedirect()
      },
      handleLogout: () => {
        auth.logout({
          returnTo: window.location.origin
        })
      }
    }
  }
})
</script>

getChirps関数をimportして、クリックイベントをトリガーに関数を叩くようにしました。

await auth.getTokenSilently() は、Auth0クライアント内に保存されているTokenを取得しています。
正しく認証をパスしていれば、getTokenSilently()を呼び出すとTokenが取得できます。



これで動作確認の準備は完了です。

動作確認

まず、localhost:8080にアクセスして、認証を要求されるか検証してください。
beforeEnterが正常に機能していれば、添付画像の画面が表示されます。
f:id:kossy-web-engineer:20210130220043p:plain

認証に成功したら、以下のような画面になります。

f:id:kossy-web-engineer:20210203222632p:plain

ここで、検証ツールを開いてから、Get Chirpsボタンを押してみましょう。

通信に成功すれば、以下のようにレコードが取得できるはずです。
f:id:kossy-web-engineer:20210203223313p:plain

先ほど、Rails側でskip_before_actionをコメントアウトしているため、chirps関連のレコードにアクセスする場合は認証を要求されるようになっているので、
認証に失敗するとHTTPステータスコード「401」が返り、成功すると「200」と共にchirpsのレコードが返ります。

検証ツール(Chromeを想定しています)で添付画像のようにNetworkタブに合わせて、XHRを選択してください。
f:id:kossy-web-engineer:20210203223140p:plain

この状態で再度Get Chirpsボタンを押してみましょう。
f:id:kossy-web-engineer:20210203223429p:plain

問題なく認証をパスすれば、上記のような画面になります。

chirpsをクリックすると、通信の詳細を確認することができます。

f:id:kossy-web-engineer:20210203223520p:plain

きちんと値を取得できていることがわかります。



まとめ

Auth0を使えば結構お手軽にログインから認証付きリクエストまで送信できることがわかりました。

他にも様々な拡張機能があるので、興味のある方は公式Docをご覧ください。
auth0.com