NextAuth.jsで保存している内容の確認とロジックについて

投稿日
NextAuth.jsで保存している内容の確認とロジックについて

Next Auth v5 + App Router の情報を記載します。

v4以前だと違う方法で取得可能だったようなのでバージョンに応じて適宜確認下さい



NextAuth.js は認証を行う際に値を保存し取り出すことが出来ます。

src/auth.ts
import NextAuth, { NextAuthConfig } from 'next-auth'
 
export const authConfig = {
  providers: [
    // メール・パスワード認証の場合
    Credentials({
      async authorize(credentials) {
        // ・・中略・・
        // ログインに成功した場合はユーザー情報を返す(他ファイルからも参照出来るように内部的に保存している)
        return isLoginSuccess ? { name: 'hoge', email: 'fuga@test.com' } : null
      },
    }),
  ],
  // ・・中略・・
} satisfies NextAuthConfig
 
// auth を使うことで他ファイルでも保存した認証情報を参照可能
export const { auth } = NextAuth(authConfig)
import { auth } from '@/auth'
 
export async function GET() {
  // 保存した値を読み込む
  const session = await auth()
  // {
  //     "session": {
  //       "user": {
  //         "name": "hoge",
  //         "email": "fuga@test.com"
  //       },
  //       "expires": "2024-08-07T07:23:11.988Z"
  //     }
  // }
}


NextAuth.jsを使い始める上で

  • 保存した値を簡単に確認する方法はないだろうか?
  • そもそも どこに・どのような方法で値が保存されている んだろうか?

という疑問が湧きました。

調べて分かったことを記載します!




先にざっくりの結論としては以下4点になります

  • 保存されている値の確認は Route Handler の確認が手軽
  • 認証時に保存した値はcookie にJWTで保存
  • ↑からdecodeすることは難しい
  • ただし、encode・decodeを自分で定義することでcookieの値からもdecode可能

検証した環境

1 next-auth 5.0.0-beta.17
2 next 14.1.3

認証時に保存した値を確認する

開発初期は専用のRoute Handlerを用意することで、保存した認証情報の確認が楽に感じました!

app/api/session-confirm/route.ts
import { NextResponse } from 'next/server'
import { auth } from '@/auth'
 
// NextAuth.jsで保存した認証情報を確認するためのエンドポイント
export async function GET() {
  const session = await auth()
  return NextResponse.json({ session })
}

未認証

認証情報がない場合にアクセスするとsession情報がないことが分かる

認証済み

認証情報がある場合にアクセスすると保存した情報が表示される


このデータはブラウザを1度落とし、しばらくしてからアクセスしても確認出来ます。

どこに、どのような方法で保存されているのでしょうか?

認証データの保存される仕組み

認証データは authjs.session-token という名前でcookieにJWTで保存されています。

cookieに authjs.session-token という名前でJWTとして保存されている




メール/パスワード認証の場合を例に、保存されるタイミングを見ていきます。


NextAuth.js の設定を定義

src/auth.ts
import NextAuth, { NextAuthConfig } from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
 
export const authConfig = {
  providers: [
    Credentials({
      async authorize(credentials) {
        // apiアクセスを再現するため2秒間待機
        await new Promise((resolve) => setTimeout(resolve, 2000))
 
        // 特定の値だった場合にログイン出来たことにする
        return credentials.email === 'test@test.com' && credentials.password === 'password'
          ? { name: 'hoge', email: 'fuga@test.com' }
          : null
      },
    }),
  ],
} satisfies NextAuthConfig
 
export const { signIn, auth } = NextAuth(authConfig)


ログインページとログイン用のServer Actionsを用意

import { login } from '@/actions/login'
 
export default function LoginPage() {
  return (
    <div>
      <h1>ログイン</h1>
      <form action={login}>
        <label>
          メールアドレス
          <input type="email" name="email" required />
        </label>
        <label>
          パスワード
          <input type="password" name="password" required />
        </label>
        <button type="submit">ログイン</button>
      </form>
    </div>
  )
}
'use server'
 
import { signIn } from '@/auth'
 
export async function login(formData: FormData) {
  // NextAuthのログイン用関数を実行
  await signIn('credentials', formData)
}

最初cookieに値がなかった状態から

ログイン後cookieに認証情報が保存されます!



先ほどのエンドポイントにアクセスすると意図した値が保存出来ていることが分かります。

認証情報がある場合にアクセスすると保存した情報が表示される

ここからはcookieに保存された authjs.session-token のJWTについて見ていきます




cookieの認証情報について

シンプルなJWTで保存されています!

{ヘッダ}.{ペイロード}.{署名}

つまり、ペイロード部分に今回保存した値も入っているわけですね。




「じゃあdecode方法が分かればdecodeして確認出来る?」

とピンと来た方もいらっしゃるかもしれません。

const cookieToken = cookies().get('authjs.session-token')
// cookieのJWTからdecode可能?
const decoded = await decode(cookieToken!.value)


その通り!…なもののここからが少し複雑です



認証情報保存の仕組み

NextAuthの認証情報、保存・取得の仕組みは内部的に以下のようになっています



保存

  1. ログイン時などに 認証情報をreturn する
  2. 1でreturnした値・secret・salt を使いNextAuthが用意している encode 関数を使ってJWTに変換
  3. 2のJWTを authjs.session-token という名前でcookieに保存

取得

  1. cookieのJWTを取得
  2. 1のJWT・secret・salt を使いNextAuthが用意している decode 関数でdecode、保存した値を使えるようにする


細かく書いたものの肝は

  • 保存・取得共に 2. でNextAuth.jsのencode・decodeがsaltを利用していること
  • その salt の値が分からないこと

です。


つまり↓の部分がNextAuth.jsの内部で隠蔽されて不明なためdecodeが出来ません

import { cookies } from 'next/headers'
import { decode } from 'next-auth/jwt'
 
export async function GET() {
  const cookieToken = cookies().get('authjs.session-token')
  const decoded = await decode({
    token: cookieToken!.value,
    secret: process.env.AUTH_SECRET,
    // saltの値が分からない・・
    salt: '',
  })
  // ・・・
}

cookieからdecode出来るようにする

ここからは本来の開発では実装の必要がなく、

NextAuth.jsと更に仲良くなるための実験的な内容です 😃


なんとかcookieの値から自分でdecodeして確認する方法はないだろうか?

と思い調べてみました。



結論としてはNextAuthの設定でencode・decodeを自分で定義することで確認が出来るようになります!


encode・decodeを定義

NextAuth.jsの設定でencode・decodeを自分で定義します。


NextAuthの encodedecode を使っているものの

saltの値は自分で定義している、というのが重要ポイント

src/auth.ts
import NextAuth, { NextAuthConfig } from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import { decode, encode } from 'next-auth/jwt'
 
export const authConfig = {
  providers: [
    Credentials({
      async authorize(credentials) {
        await new Promise((resolve) => setTimeout(resolve, 2000))
        return credentials.email === 'test@test.com' && credentials.password === 'password'
          ? { name: 'hoge', email: 'fuga@test.com' }
          : null
      },
    }),
  ],
 
  jwt: {
    encode: async ({ token, secret }) => {
      // カスタムのencode方法
      return encode({ token, secret, salt: 'your-custom-salt' })
    },
    decode: async ({ token, secret }) => {
      // カスタムのdecode方法
      return decode({ token, secret, salt: 'your-custom-salt' })
    },
  },
} satisfies NextAuthConfig
 
export const { signIn, auth } = NextAuth(authConfig)

cookieからdecodeする

あとはRoute Handlerでcookieの値からdecodeする際にも同じsaltを利用します

app/api/session-confirm/route.ts
import { NextResponse } from 'next/server'
import { auth } from '@/auth'
import { cookies } from 'next/headers'
import { decode } from 'next-auth/jwt'
 
export async function GET() {
  const cookieToken = cookies().get('authjs.session-token')
  const decoded = await decode({
    token: cookieToken!.value,
    secret: process.env.AUTH_SECRET,
    salt: 'your-custom-salt',
  })
  console.log('decoded:', decoded)
 
  const session = await auth()
  return NextResponse.json({ session })
}

decoded は以下のように出力されます

decoded: {
  name: 'hoge',
  email: 'fuga@test.com',
  sub: '3793d34a-2f0e-4060-a2a0-4bf50733503c',
  iat: 1720433662,
  exp: 1723025662,
  jti: 'f06c7e97-dd81-4d6f-bf24-2c474fe2b1fa'
}


cookieのJWTからも認証情報を取得出来るようになりました!🙌

プロフィール画像
Yuki Takara
都内でフリーランスのエンジニアをやってます。フロントとアプリ開発メインに幅広くやってます。