🫐

Next.js + ClerkでログインしたユーザーをRailsバックエンドで認証・検証する方法

に公開

初めまして!
スペースマーケットでインターンをしている@aki_apintです!

今回は、
Next.js × Clerk のログインは簡単にできたけど、
「Rails側で、Clerkユーザーってどう取得するの?」って話。

個人開発で使っている Next.js + Clerk + Rails の構成。
フロントのClerkは一瞬で導入できたのに、バックエンドのRailsとどうつなぐかでめちゃくちゃハマりました。

同じように「Clerk、簡単そうなのにRailsとどう連携するの?」と悩んでいる人に向けて、
連携の仕組みと、実際に僕が実装した構成をまとめました!
少しでも助けになれれば嬉しいです!!

この記事でやること

  • Clerkログイン済みユーザーのJWTを取得
  • JWTをRailsに送信して検証
  • Rails側でユーザーIDを取得

やりたいこと

フロントでClerkにログインしたユーザーを、バックエンド(Rails)側で取得して、投稿などのテーブルと紐付けたい!

背景

自身のサービスに、Googleアカウントなどでログインしたい!
ということでOAuth認証を実装することに。

調べていったら、Clerkが簡単そう!
(爆速でNext.jsに実装可能だって!?)

Clerkとは、Webアプリケーションの認証・認可を簡単に行えるプラットフォーム。
手軽にOAuth認証などを実装できるほか、
Next.js、React、Remixなどのフロントエンドフレームワーク向けのSDKを提供しており、高機能な認証処理を効率的に実装できる。

ほんとに一瞬でNext.jsに実装できた!

じゃあこのままRails側でもClerkのユーザーを使って投稿機能を実装しよう!って、
バックエンドでClerkのユーザー情報をどうやって取得するんだ??

そもそもClerkから、どうやってユーザー情報を取得するの??

Clerkでは、ユーザーがログインすると**JWT(JSON Web Token)**付きのセッションが生成されます。バックエンド(例:RailsやNode.jsなど)では、このJWTを使ってユーザーを認証し、ユーザー情報を取得できます。

つまり、Clerkが生成するJWTを検証すれば、「このユーザーは誰なのか?」がわかるという仕組み!

JWTって何?

JWTとは?
JWT(JSON Web Token)は、主に認証や認可のために利用されるトークン形式です。JSON形式で、HTTPヘッダーに格納して送受信を行えます。ユーザーの情報を含んだ署名付きのトークンで、サーバーはこのトークンを受け取り、JWKを使って検証を行うことで「正しいユーザーかどうか」判断できます。

JWKとは?
JWK(JSON Web Key)とは、JWTの検証を行うための公開鍵のことで、Clerkのダッシュボードから取得できます。
つまり、JWKがないとJWTを検証できない!

環境変数として**.env**などで宣言しておき、Rails側で取得してそれを元に署名をチェックを行います。

具体的な実装

ここまでで、ようやくバックエンド(Rails)側でClerkのユーザー情報を取得する方法がわかったので、いよいよ詳しい実装内容に入っていきます!

1. フロントでJWTを取得してバックエンドに送る

フロントでのJWTの取得

ではまず、フロント(Next.js)でログインしているClerkユーザーのJWTを取得しましよう。
ユーザーコンポーネントでのClerkのJWTの取得方法は以下のようになります。

'use client'
import { useAuth } from "@clerk/nextjs";

const { getToken } = useAuth();
const token = await getToken(); // ←ここでClerkのJWTを取得

バックエンドへJWTを送る

バックエンド(Rails)側へJWTを送り検証を行ってもらうためには、先ほど取得したJWTをAuthorizationのBearerトークンとしてヘッダーに設定し送る必要があります。

// バックエンドAPIにユーザー確認をリクエスト
const res = await fetch(`http://localhost:3001/api/user_check`, { // バックエンドAPIのエンドポイント
    method: 'GET',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${token}`, // 先ほど取得したJWTをセット
    }
})

フロント(Next.js)側のコード全体

上の処理を含めたコード全体は以下のようになります!

'use client'
import { useEffect } from "react";
import { useAuth } from "@clerk/nextjs";

export default function CheckUserPage() {
  const { getToken } = useAuth();

  useEffect(() => {
    const checkUser = async () => {
      const token = await getToken(); // ←ここでClerkのJWTを取得
      if (!token) return;

      const res = await fetch(`http://localhost:3001/api/user_check`, {
        method: 'GET',
        headers: {
          'Authorization': `Bearer ${token}`, // ←ヘッダーにBearerトークンとして格納
          'Content-Type': 'application/json'
        }
      });

      if (res.ok) {
        console.log("✅ ユーザー検証成功");
      } else {
        console.error("❌ ユーザー検証失敗");
      }
    };

    checkUser();
  }, []);

  return (
    <div className="text-center mt-10 text-lg">
      ユーザー確認中です... // ユーザーの確認中に表示
    </div>
  );
}

2. バックエンド(Rails)側での前準備

先ほどまででフロント側の実装ができたので、今度はバックエンド側でJWTを検証するロジックを実装していきましょう!
前準備として、処理で使う環境変数を設定する必要があります!
ここで設定するのは、Clerkのダッシュボードで取得できるJWK(公開鍵)です。

ClerkのダッシュボードでJWKを取得

  • まず、Clerkダッシュボードでconfigを開きます
  • 次に、API Keyセクションを選択し、右側にあるJWKs public Keyをコピーします。

環境変数の設定

先ほど取得したJWKを.envファイルに設定します。

# /.env
CLERK_JWKS_URL=https://possible-lacewing-XXXXXXXXXXXXX
          (↑先ほど取得したJWK)

3. バックエンド(Rails)側でJWTの検証

前準備が終わったので、検証処理の実装に移ります。

JWT検証処理

  • Clerkの公開鍵(JWK)セットを取得し、JSONからRubyのハッシュ(配列)に変換。
jwks_raw = Net::HTTP.get(URI(jwks_url)) # ClerkのJWKのURLを叩き鍵セットを取得
jwks_keys = JSON.parse(jwks_raw)["keys"] # rubyのハッシュ型に変換
  • JWT ライブラリが理解できる形式に変換、複数の鍵がある場合にも対応。
jwk_set = JWT::JWK::Set.new(jwks_keys.map { |jwk| JWT::JWK.import(jwk) })
  • JWTをdecode(デコード)する。
    ClerkのJWT署名アルゴリズムとしてRS256を指定し、先ほど取得した鍵セットを使ってJWTを検証する。decoded_tokenに結果を格納。(decoded_tokenはハッシュとなっている)
decoded_token, = JWT.decode(token, nil, true,
  algorithms: ["RS256"],
  jwks: jwk_set
)

JWTを検証するサービスクラスのコード全体

JWTの検証ロジック

# app/services/clerk/jwt_verifier.rb
require 'net/http'
require 'uri'
require 'json'
require 'jwt'

module Clerk
  class JwtVerifier
    def self.verify(token)
      jwks_url = ENV.fetch("CLERK_JWKS_URL", "https://api.clerk.dev/.well-known/jwks.json")
      jwks_raw = Net::HTTP.get(URI(jwks_url))
      jwks_keys = JSON.parse(jwks_raw)["keys"]
      jwk_set = JWT::JWK::Set.new(jwks_keys.map { |jwk| JWT::JWK.import(jwk) })

      decoded_token, = JWT.decode(token, nil, true,
        algorithms: ["RS256"],
        jwks: jwk_set
      )
      decoded_token
    rescue => e
      Rails.logger.error "❌ Clerk認証失敗: #{e.class} - #{e.message}"
      nil
    end
  end
end

JWT検証を行うクラスの使用例

フロントからのレスポンスからJWTを取り出す

まず、フロントからのリクエストに、AuthorizationのBearerトークンとしてヘッダーに設定されているJWTを取得します。

token = request.headers["Authorization"]&.split&.last # リクエストからJWT部分を取得

JWT検証クラスの呼び出し

先ほど作成したクラスによって、以下の形式でJWT検証処理を呼び出しる用になっています!

payload = Clerk::JwtVerifier.verify(token)

user_id = payload["sub"] # 認証OK: user_idをもとにUserを探すなど

ここまで来ればようやくJWTの検証を行う全ての準備が完了しました!!

以下は、JWTの検証を行う例の紹介になります!

ps:JWT検証の使用例

以下はJWT検証の使用例になります。

コントローラーでJWT検証を全てのアクションに対して行う

リクエストからトークンを取り出し、先ほどのJWT検証関数を呼び出してUserの認証を行うという処理を、全てのアクション(ページの読み込み)前に実行するよう指定しています。
こうすることで、ログインが確認できるユーザーのみがアクションを行えるようになります!

class Api::UsersController < ApplicationController
  before_action :authenticate_clerk! # 全てのアクション前にJWT検証を行うよう指定


  # POST /users
  def create
  end

  # GET /users/:id
  def show
    user = User.find(params[:id])
    render json: user
  end

  # /users/:id (※本人のみ更新可)
  def update
    if current_user.update(user_params)
      render json: current_user
    else
      render json: { error: current_user.errors.full_messages }, status: :unprocessable_entity
    end
  end


  private

  # ユーザー登録/更新に使うパラメータ
  def user_params
    params.require(:user).permit(:username, :profile)
  end

  # Clerk JWT を検証して current_user をセット
  def authenticate_clerk!
    token = request.headers["Authorization"]&.split&.last #リクエストからJWT部分を取得
    return head :unauthorized unless token

    decoded_token = Clerk::JwtVerifier.verify(token) # JWT検証クラスの呼び出し、
    return head :unauthorized unless decoded_token

    clerk_user_id = decoded_token["sub"] # デコード結果からUserIDの部分を取得
    Rails.logger.debug "Clerk User ID: #{clerk_user_id}"
    return head :unauthorized unless clerk_user_id

    @current_user_clerk_id = clerk_user_id
  end

end

まとめ

今回は、Clerkでログインしたユーザーを、JWTを使用してRails側で検証する仕組みについて紹介しました。

  1. フロントでJWTを取得
  2. ヘッダーのAuthorizationとしてJWTを設定し、リクエスト
  3. バックエンドでJWKを使いJWTを検証
  4. コントローラーでの使用例

発展として、独自のデータベースを使用して、投稿などのテーブルとClerkのユーザーを紐付ける場合は、ClerkのUserIDをJWTを検証して取得し、自前のUserテーブルに格納する必要があります。
今回説明した部分でもClerkのUserIDを取得する部分があるので、そちらを自前のUserテーブルに格納していただければと思います!

読んでいただきありがとうございました!

この記事が、同じように「Next.js + Clerk + Rails」で悩んでいる方の助けになれば嬉しいです。

スペースマーケット Engineer Blog

Discussion