eityansメモ

eityansメモ

ゆるくやっていきます

記事一覧

Next.js(Vercel) + Rails + Auth0を使ってログイン機能を作る

Next.js(Vercel)でフロントエンド、Railsでバックエンドを用意する。結構やることが多かったのでメモ。

原則は公式のドキュメントを参照

大きな流れ

なかなか全体像がつかみにくかった。今回取り組んでわかったことをまとめる。

  1. ユーザーはNextからログインや登録を行い、Auth0に遷移する。

  2. Auth0に遷移し、そこで認証を行う。認証が成功するとアクセストークンがJWTとして取得できるようになる

  3. Next側でそのJWTを使ってRailsに情報を連携する

  4. Rails側でJWTをパースし、問題なかったら処理を実行する

こういうことを行うためにこれから色々設定をする。


Auth0の設定

アプリケーションを作る

Reguler Web Applicationsを選択。アプリケーションを作ると、QuickStartでフレームワークごとの設定が案内されるので、原則そらのドキュメントに従う。

コールバックの設定

ローカル用と、本番のそれぞれを入力する。後述するSDKを用いればそのあたりのエンドポイントは勝手に作られる。

Next.js側の設定

Auth0 Next.js SDKをインストール

1npm install @auth0/nextjs-auth0

dotenvで環境変数を設定する

どう設定すれば良いのかはドキュメントに記載されてる。

SDK Clientを作る

サンプルをベースに少し改造

1// lib/auth0.js
2
3import { Auth0Client } from "@auth0/nextjs-auth0/server";
4
5export const auth0 = new Auth0Client({
6  domain: process.env.AUTH0_DOMAIN,
7  clientId: process.env.AUTH0_CLIENT_ID,
8  clientSecret: process.env.AUTH0_CLIENT_SECRET,
9  appBaseUrl: process.env.APP_BASE_URL,
10  secret: process.env.AUTH0_SECRET,
11
12  authorizationParameters: {
13    scope: process.env.AUTH0_SCOPE,
14    audience: process.env.AUTH0_AUDIENCE,
15  }
16  async onCallback(error, context, session) {ユーザー作成の処理}
17});
ここでonCallbackを設定して、認証後にユーザーの作成を実施するようにしている。

Middlewareの作成

このサンプルもドキュメントから。

1import type { NextRequest } from "next/server";
2import { auth0 } from "./lib/auth0";
3
4export async function middleware(request: NextRequest) {
5  return await auth0.middleware(request);
6}
7
8export const config = {
9  matcher: [
10    "/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)",
11  ],
12};

ここまででログインやログアウトのエンドポイントが作られる。

ログインボタンの設置

1<a href="/auth/login">ログイン/新規登録</a>

ここでLinkなどを使うと先読みが入ってややこしくなるらしい。

Vercel側の設定

環境変数を設定する

設定した環境変数

  • AUTH0_CLIENT_ID

  • AUTH0_CLIENT_SECRET

  • AUTH0_SECRET

  • AUTH0_DOMAIN:https://を取り除かないとエラーがでる

  • AUTH0_AUDIENCE

  • AUTH0_SCOPE

  • APP_BASE_URL

最初環境変数の設定をミスっていてローカルでは問題ないのに本番でMIDDLEWARE_INVOCATION_FAILEDエラーが発生して詰まっていた。

Railsとの連携

この記事がわかりやすい。これに従って設定する。

Auth0APIの作成

Auth0のダッシュボードで作成。

Identifierと、ドメインの情報を記録する。これをRailsの設定に使う。

jwt gemの追加

1gem 'jwt'

Identifierと、ドメインの情報をクレデンシャルに追加

1EDITOR="vim" bundle exec rails credentials:edit --environment development
1auth0:  
2  api_identifier: xxx
3  domain: xxx

ここでのdomainはhttps://を含まない

JWTの検証をするクラスを作成

このあたりもドキュメントのママ

1# app/lib/json_web_token.rb
2require 'net/http'
3require 'uri'
4
5class JsonWebToken
6  def self.verify(token)
7    JWT.decode(token, nil,
8               true, # Verify the signature of this token
9               algorithm: 'RS256',
10               iss: "https://#{Rails.application.credentials.auth0[:domain]}/",
11               verify_iss: true,
12               aud: Rails.application.credentials.auth0[:api_identifier],
13               verify_aud: true) do |header|
14      jwks_hash[header['kid']]
15    end
16  end
17
18  def self.jwks_hash
19    jwks_raw = Net::HTTP.get URI("https://#{Rails.application.credentials.auth0[:domain]}/.well-known/jwks.json")
20    jwks_keys = Array(JSON.parse(jwks_raw)['keys'])
21    Hash[
22      jwks_keys
23      .map do |k|
24        [
25          k['kid'],
26          OpenSSL::X509::Certificate.new(
27            Base64.decode64(k['x5c'].first)
28          ).public_key
29        ]
30      end
31    ]
32  end
33end

認証周りのサービスクラスを作成

TokenVerificationを作り、認証してユーザーを返すAuthorizationServiceと、新規にユーザーを作成するUserRegistrationServiceを作る

1module TokenVerification
2  private
3
4  def http_token
5    if @headers["Authorization"].present?
6      @headers["Authorization"].split(" ").last
7    end
8  end
9
10  def verify_token
11    JsonWebToken.verify(http_token)
12  end
13end

1class AuthenticationService
2  include TokenVerification
3
4  def initialize(headers = {})
5    @headers = headers
6  end
7
8  def perform!
9    payload, _header = verify_token
10    User.find_by!(sub: payload["sub"])
11  end
12end

1class UserRegistrationService
2  include TokenVerification
3
4  def initialize(headers = {})
5    @headers = headers
6  end
7
8  def perform!
9    payload, _header = verify_token
10    User.find_or_create_by(sub: payload["sub"])
11  end
12end

認証用のコントローラーの作成

認証に成功したらユーザーを返す

1class SecuredController < ApplicationController
2  before_action :authorize_request
3
4  private
5
6  def authorize_request
7    @current_user = AuthenticationService.new(request.headers).perform!
8  rescue JWT::VerificationError, JWT::DecodeError
9    render json: { errors: [ "Not Authenticated" ] }, status: :unauthorized
10  end
11
12  def current_user
13    @current_user
14  end
15end

ユーザーの作成はUsersControllerに作成

1class UsersController < SecuredController
2  skip_before_action :authorize_request, only: [ :create ]
3
4  def create
5    user = UserRegistrationService.new(request.headers).perform!
6    if user.save
7      render json: user, status: :created
8    else
9      render json: { errors: user.errors.full_messages }, status: :unprocessable_entity
10    end
11  end
12end

これでログインまわりの機能ができた