@supabase/ssrでサーバー側にてユーザー情報を取得するときは、getUserを使おう

この記事では、Supabaseを使用してNode.jsアプリケーションでユーザー情報を取得する方法が紹介されています。具体的には、サーバー側でStripeの契約情報を取得し、ログイン中のユーザー情報を利用してリダイレクトする機能を実装しています。初期実装ではauth.getSession()を使用していましたが、より安全な方法としてauth.getUser()を使用することが推奨されています。ユーザー情報の取得は明示的に行う必要があります。

広告ここから
広告ここまで

目次

    この記事では、Supabaseを使ったNode.js系アプリケーション(Remixなど)で、ユーザー情報をサーバー側の処理にて取得する方法を簡単に紹介します。

    作りたかったもの

    認証付きのウェブアプリケーションにて、ログイン中のユーザー情報を利用したサーバー側の処理を作ろうとしました。具体的には、ユーザーIDからStripe上にある契約情報を取得し、その内容に基づいてリダイレクトを実施するような機能です。この機能を実装するには、ページをレンダリングする前にサーバー側で情報取得とリダイレクトするか否かの判定が必要でしたので、Supabaseのユーザー情報をサーバー側で取得する必要があります。

    auth.getSession()の内容をそのまま使うのは非推奨らしい

    まず初めに実装したコードは、次のようなものです。ログイン中かどうかの判定にauth.getSession()を利用しますので、そのレスポンスにあるユーザー情報を利用しています。

    export const createSupabaseServerClient = ({context, request}: {
        context: {
            cloudflare: {
                env: Pick<Env, 'SUPABASE_KEY' | 'SUPABASE_URL'>
            }
        },
        request: Request
    }, headers: Headers) => {
        const supabaseClient = createServerClient(
          context.cloudflare.env.SUPABASE_URL, 
          context.cloudflare.env.SUPABASE_KEY, {
          cookies: {
            getAll() {
              return parseCookieHeader(request.headers.get('Cookie') ?? '')
            },
            setAll(cookiesToSet) {
              cookiesToSet.forEach(({ name, value, options }) => {
                headers.append('Set-Cookie', serializeCookieHeader(name, value, options))
              })
            }
          },
        })
        return supabaseClient
    }
    
    export async function loader({params, context, request}: LoaderFunctionArgs) {
      const headers = new Headers()
      const supabaseClient = createSupabaseServerClient({context, request}, headers)
      const user = await supabaseClient.auth.getSession()
        .then(({data: {session}}) => {
          if (!session) return null;
          return session.user
        })
    
      return json({    user,
      })
    }

    しかしこの実装では、サーバー側のリクエストログに警告メッセージが出てきます。警告文をざっと翻訳して読むと、どうやらステートが変わった時に古い情報を参照してしまう可能性があるらしいです。古い情報に基づいた処理が走らないようにするためには、明示的にユーザー情報を取得する必要があるとのことでした。

    Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and many not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server.

    セッションの取得と、ユーザー情報の取得は個別かつ明示的に実装しよう

    ということで、ユーザー取得処理を次のように変更しました。

      const user = await supabaseClient.auth.getSession()
        .then(({data: {session}}) => {
          if (!session) return null;
          return supabaseClient.auth.getUser()
            .then(({data: { user}}) => user)
        })

    async / awaitとPromiseが混ざっていますが、user変数がnull or Userになるような処理を関数化せずに作りたいという横着からこの実装にしています。そのため混ざっていることが気になる方は、関数化しておく方が良いでしょう。

    async function getUserFromSession(supabaseClient, session) {
      if (!session) return null;
      return supabaseClient.auth.getUser()
    }

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark