未分類

【Next.js × Rail...

s】JWT認証をセキュアに実装する方法|HttpOnly Cookie+Server Action構成解説

2025

【Next.js × Rail... s】JWT認証をセキュアに実装する方法|HttpOnly Cookie+Server Action構成解説

23.07.2025
0 コメント
264 文字
約 14 分で読めます
📖 【Next.js × Rails】JWT認証をセキュアに実装する方法|HttpOnly Cookie+Server Action構成解説
約 14 分で読めます

localStorageやsessionStorageでは実現できない、HttpOnly Cookieによる安全なJWT認証管理とその実装方法を徹底解説します。

📝 この記事を書くに至るまで

とあるプロジェクトで RailsNext.js を組み合わせて開発していました。Rails 側では認証系で有名な devise-jwt を使用し、Next.js 側からログイン・ログアウトなどの認証機能をつなぎ込んでいました。

まずぶつかった最初の壁は、JWTをどこに保管すべきか という問題でした。多くの記事を読み漁りましたが、どれも localStorage やインメモリでの保管を推奨しているように見受けられました。

しかし、JSでアクセス可能なこれらの手法にはセキュリティ上のリスク(XSS)があるため、もっと安全な方法を探してたどり着いたのが、HttpOnly Cookie での管理でした。

ただしここで第2の壁に直面します。それは「HttpOnly Cookie を操作するにはサーバー側でのみ可能」という制約。Next.js のサーバーアクションやコンポーネント構成を深く理解していないと、実装が難航します。

本記事では、このプロセスで私がぶつかった壁と、それをどのようにして乗り越えたかを解説していきます。特に、App RouterとRailsの組み合わせでセキュアかつ快適にJWT認証を実装したい方の参考になれば幸いです。


✅ 本記事の対象読者

  • RailsとNext.jsを組み合わせて認証機能を構築したい人
  • JWTの保管方法に悩んでいる人
  • HttpOnly CookieとServer Actionの使い方を知りたい人
  • Next.js App Routerでの認証管理に困っている人

🔐 JWTはどこに保存すべき?

RailsのAPI側で devise-jwt を使用する際、JWTの保存先が大きな検討ポイントになります。

❌ localStorage / sessionStorage の課題

  • JavaScriptからアクセス可能 → XSSのリスク
  • ページリロードで状態が消える
  • 自動でHTTPリクエストに含まれない

✅ 解決策:HttpOnly Cookieの活用

HttpOnly 属性付きのCookieにJWTを格納することで、以下のようなセキュリティ上のメリットがあります。

  • JavaScriptからアクセス不可
  • HTTPリクエストに自動で付与される
  • 状態が持続される

⚙️ Next.js × Railsでの認証構成

🍪 Cookie操作はServer Componentでのみ可能

Next.jsではCookieの読み書きは Server Action 内で行う必要があります。

🔁 useActionStateでClient→Server通信

useActionState を使えば、クライアントからサーバー関数を安全に呼び出すことが可能です。

💡 実装例

// Client Component
'use client'

const [state, formAction] = useActionState(loginAction, initialState)

<form action={formAction}>
  <input name="email" />
  <input name="password" type="password" />
  <button type="submit">ログイン</button>
</form>
// Server Action
'use server'

export async function loginAction(formData: FormData) {
  const email = formData.get('email')
  const password = formData.get('password')

  const token = await loginAndGetJwt(email, password)

  cookies().set('access_token', token, {
    httpOnly: true,
    path: '/',
    secure: true,
    sameSite: 'lax',
  })

  return { success: true }
}
// layout.tsx(Server Component)
const user = await getCurrentUser()

return <AuthProvider user={user}>{children}</AuthProvider>

🤔 よくある誤解:use clientの影響範囲

「use client を宣言すると子要素すべてがClient Componentになる」と誤解されがちですが、実際にはそのファイル内だけに影響します。

✅ OKな構成例

// layout.tsx(Server Component)
import AuthProvider from '@/providers/AuthProvider' // Client Component
import { getCurrentUser } from '@/lib/auth'         // Server function

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const user = await getCurrentUser()

  return (
    <html lang="ja">
      <body>
        <AuthProvider user={user}>
          {children} {/* ここは Server Component もOK */}
        </AuthProvider>
      </body>
    </html>
  )
}

✅ 実装構成まとめ(表形式)

項目 手法
JWT保管 HttpOnly Cookie によるサーバー側管理
Client→Server呼び出し useActionState + server action
ログイン状態の維持 layout.tsx にて /me を取得し Provider に渡す
コンポーネント分離 Server Component / Client Component を明確に使い分け

📌 まとめ

セキュアなJWT認証をNext.js(App Router)とRails(devise-jwt)で構築するには、HttpOnly Cookieの活用と、useActionState + server actionによる連携が鍵となります。

ぜひ、今回紹介したベストプラクティスを参考に、より安全かつ快適な認証設計を実現してください。

この記事が役に立ったらシェアしてください