💭

【Next.js】Auth0で認証したユーザのデータを取得してみた

2021/10/31に公開

この記事ではAuth0でログインしたユーザに紐付いたデータを取得してみます。
下記の記事で紹介している自作サイトでかなり苦労した所でもあります。
https://zenn.dev/tasuya/articles/aea8fe1b817e06
他にもっといい方法があったらコメントで教えてください!

環境の構築やフロント側のAuth0との連携は割愛させていただきます。
必要な方はこちらを参考にしてみてください!
https://zenn.dev/tasuya/articles/5b3d584b1b0e20
https://zenn.dev/tasuya/articles/238a5da567ba49

使用技術

Next.js 動的ルーティングが便利なため
Axios
Ruby on Rails
MySql
Auth0 ログイン機能

どうやって実現するのか

フロントの動き

まずフロント側でAuth0に認証します。認証をするとユーザIDAccessTokenを保持できます。
ユーザのデータ取得時に保持しているAccessTokenをバックエンドに送ります。

バックエンドの動き

バックエンドでは送られて来たAccessTokenを使ってAuth0に認証済みのAccessTokenなのか確認します。認証済みならユーザIDに紐付いたデータをDBから取得してフロントに送ります。

Railsの設定

フロントと通信できるように設定を行っていきます。

CORS設定

RailsにCORSの設定を行いましょう。

まずはGemfilerack-corsのコメントを外しましょう。

api/Gemfile
gem 'rack-cors'  # コメントを外す

コメントを外せたらBundle installしておきましょう。

docker-compose build
docker-compose run --rm api bundle install

続いてrack-corsの設定ファイルを編集します。
originsにはfront側のURLを指定します。

api/config/initializers/cors.rb
 Rails.application.config.middleware.insert_before 0, Rack::Cors do
   allow do
     origins 'localhost:8000'

     resource '*',
       headers: :any,
       methods: [:get, :post, :put, :patch, :delete, :options, :head], :expose => ['access-token']
   end
 end

ここではmethodsに指定している、exposeが大事になります。
exposeはバックエンドがheaderで受取たいものを指定します。

Modelの作成

テスト用のデータを作成して行きましょう。
今回はListテーブルにtitlebodyと特定のユーザのデータを取得するためにuser_idカラムを作成します。

docker-compose run api bundle exec rails g model List title:string body:text userId:string
docker-compose run api bundle exec rails db:migrate

テーブルの作成ができたので、データを入れていきます。
まずはseeds.rbに入れるデータを書きます。
今回はテストも兼ねて取得しないデータも記載します。

api/db/seeds.rb
5.times do |number|
     List.create(title: "取得しないでね#{number}", body: "取得してないよね#{number}", user_id: "01")
    List.create(title: "取得すべき#{number}", body: "取得できてる#{number}", user_id: "autn0のユーザID")
end

Auth0のユーザIDはここで確認できます。
Auth0にログインしてusersページにいきます。

ユーザIDと書いてある所にauth0|から始まるIDを入力します。

できたらデータを入れましょう。

docker-compose run api bundle exec rails db:seed

Controllerの作成

続いてコントロールを作成しましょう。
routes.rb'に追加します。 '''ruby:api/config/routes.rb Rails.application.routes.draw do namespace :api do resources :list, only: :index end end ''' api/app/conrtollersディレクトリ内にapi`ディレクトリを作成します。

cd api/app/controllers/
mkdir api

作成したapiディレクトリ内にList_controller.rbを作成します。

cd api
touch List_controller.rb

編集しましょう。

api/app/controllers/api/List_controller.rb
class Api::ListController < ApplicationController
    def index
        @List = List.all
        render json: @List
    end
end

ここまでできたらデータが取得できているか確認してみましょう!
データが表示されたら成功です!
http://localhost:3000/api/list

Next.js非同期通信処理

次はフロント側の実装です。
フロントからAPIにuserIdAccessTokenを送ります。
※Auth0との認証方法は割愛します。

axios導入

axiosを使ってAPI通信するので、インストールします。

docker-compose run front npm --prefix ./ディレクトリ名 install axios

データ取得

データ取得処理を実装していきます。
まず環境変数を指定するためenvファイルを作成します。

touch .env.local

.env.localファイルにAPIのURLを指定して起きます。

.env.local
NEXT_PUBLIC_API_URL="http://localhost:3000"

pagesディレクトリ内にlist.tsxファイルを作成します。

cd pages
touch list.tsx

list.tsxを編集します。

pages/list.tsx
import React, {useEffect, useState} from "react";
import { useAuth0 } from "@auth0/auth0-react";
import axios from "axios";

interface Contents {
    title: string,
    bodu: string
}

const List: React.FC= () => {
    const {getAccessTokenSilently, isAuthenticated} = useAuth0();
    const [list, setList] = useState<Contents[] | undefined>(undefined);
    
    const getList = async() => {
        const token = await getAccessTokenSilently();
        const url = process.env.NEXT_PUBLIC_API_URL;
        const res = await axios.get<Contents[]>(url +'/api/list/', { headers: {
        Authorization: "Bearer " + token
        }, 
        })
        setList(res.data)
    }

    useEffect(() => {
      getList()
    }, [])


    return(
        <div>
            {list === undefined ? 'データが存在しません' :
                <ul>
                    {list.map((item, index) => (
                    <li key={index}>{item.title}</li>
                    ))}
                </ul>
                }
        </div>
    );

} 
export default List;

上記のコードで大事な部分を抜粋しました。

const token = await getAccessTokenSilently();
const res = await axios.get<Contents[]>(url +'/api/list/', { 
	headers: {
	   Authorization: "Bearer " + token
        }, 
        })

ログイン認証して証明のTokenを取得して、これを送っています。
こうすれば誰のデータが必要かAPIに知らせることができます。

Rails認証設定

次はフロントから送られてきたトークンをAuth0に問い合わせをして、ログイン済みユーザなのか確認します。

JWT追加

JWTを追加します。

Gemfile
gem 'JWT'
docker-compose build
docker-compose run --rm api bundle install

認証処理追加

こちらを元に実装していきます。
https://auth0.com/docs/quickstart/backend/rails/01-authorization

lib/json_web_token.rbを作成して編集します。

cd lib
touch json_web_token.rb

``

api/lib/json_web_token.rb
# lib/json_web_token.rb

# frozen_string_literal: true
require 'net/http'
require 'uri'

class JsonWebToken
  def self.verify(token)
    JWT.decode(token, nil,
               true, # Verify the signature of this token
               algorithms: 'RS256',
               iss: 'https://YOUR_DOMAIN/',
               verify_iss: true,
               aud: Rails.application.secrets.auth0_api_audience,
               verify_aud: true) do |header|
      jwks_hash[header['kid']]
    end
  end

  def self.jwks_hash
    jwks_raw = Net::HTTP.get URI("https://YOUR_DOMAIN/.well-known/jwks.json")
    jwks_keys = Array(JSON.parse(jwks_raw)['keys'])
    Hash[
      jwks_keys
      .map do |k|
        [
          k['kid'],
          OpenSSL::X509::Certificate.new(
            Base64.decode64(k['x5c'].first)
          ).public_key
        ]
      end
    ]
  end
end

json_web_token.rbが使用できるようにconfig/initializersディレクトリ内にjwt.rbを作成して編集します。

config/initializers/jwt.rb
require './lib/json_web_token'

次にapp/controllers/concerns/secured.rbを作成して編集します。

app/controllers/concerns/secured.rb
# app/controllers/concerns/secured.rb

module Secured
  extend ActiveSupport::Concern
  included do
    before_action :authenticate_request! 
  end

  private

  def authenticate_request!
    @auth_user_id = auth_token[0]['sub'] 
  rescue JWT::VerificationError, JWT::DecodeError
    render json: { errors: ['Not Authenticated'] }, status: :unauthorized
  end

  def http_token
    if request.headers['Authorization'].present?
      request.headers['Authorization'].split(' ').last
    end
  end

  def auth_token
    JsonWebToken.verify(http_token)
  end
end

上記コードは認証完了後、@auth_user_idにユーザーIDを入れるようにしてあります。

最後にList_controller.rbに認証処理が動く用に編集します。

app/controllers/List_controller.rb
class Api::ListController < ApplicationController
     include Secured

    def index
        @List = List.where(userId: @auth_user_id)
        render json: @List
    end
end

動作確認

ここまできたら確認してみましょう!

docker-compose up -d

ログイン後http://localhost:8000/listにアクセスすると

ユーザーのデータだけが無事に表示されました!

他にもっといい方法があれば教えていただけると幸いです!

終わり

ここまで見てくださりありがとうございました!

ツイッターもやっているのでぜひフォローもお願いします!
Twitter

Discussion