LoginSignup
28
7

NextAuthを完全に理解する #2

Last updated at Posted at 2022-02-06

はじめに

最近,Next.jsを使ったwebアプリケーションを作り始めました.この中でログイン機能を付けたくてNextAuth.jsの導入をしたのですが,無知すぎて何をしているのかわかりませんでした.そこで,勉強を兼ねてここにドキュメントの内容を日本語訳して記しておきます.

2023/06/11 追記
NextAuth.jsはAuth.jsに変わりました.Next.js App Routerの場合の実装も併せて新しく記事を作成したのでそちらをご覧ください.

https://qiita.com/kage1020/items/fca49e9b42b972b70b8c

この記事の内容

この記事はNextAuthのドキュメントを私なりに解釈して書き留めたものです.いくつか誤りがあると思いますのでコメント等で指摘していただくと幸いです.
ドキュメントを読みたい方は以下からどうぞ.

前回の記事は以下からどうぞ.

今回はclient sideについてです.

2022/02/21現在の最新バージョンは4.2.1であり,この記事はその時のドキュメントを元に書いています.
2022/09/25 追記
最新バージョン4.10.3に更新しました.

Sessionの取得

ページやコンポーネント内でセッションを取得するにはuseSession Hookを使います.

example
import { useSession } from "next-auth/react"

export default function Component() {
  const { data: session, status } = useSession()

  if (status === "authenticated") {
    return <p>Signed in as {session.user.email}</p>
  }

  return <a href="/api/auth/signin">Sign in</a>
}

useSessiondatastatusを返します.dataの型は

  • fetch前はundefined
  • fetchに失敗した場合はnull
  • fetchに成功した場合はSession

です.Sessionオブジェクトは

Session
{
  user?: {
    name?: string | null
    email?: string | null
    image?: string | null
  }
  expires?: string
}

statusの型は"loading" | "authenticated" | "unauthenticated"となります.次回の記事にてこのSessionの拡張方法を記載します.

サインインしていない場合の挙動

上手く理解できなかったので原文を貼ります.

Due to the way how Next.js handles getServerSideProps and getInitialProps, every protected page load has to make a server-side request to check if the session is valid and then generate the requested page (SSR). This increases server load, and if you are good with making the requests from the client, there is an alternative. You can use useSession in a way that makes sure you always have a valid session. If after the initial loading state there was no session found, you can define the appropriate action to respond.

The default behavior is to redirect the user to the sign-in page, from where - after a successful login - they will be sent back to the page they started on. You can also define an onFail() callback, if you would like to do something else:

import { useSession } from "next-auth/react"

export default function Admin() {
  const { status } = useSession({
    required: true,
    onUnauthenticated() {
      // The user is not authenticated, handle it here.
    }
  })

  if (status === "loading") {
    return "Loading or not authenticated..."
  }

  return "User is logged in"
}

onUnauthenticated()でsign-inページへのリダイレクトを防げる?

Custom Client Session Handling

getServerSidePropsはクエリごとにセッションが有効かを判定するため通信回数が増える.そこでコンポーネントにkey-valueを与えることで判定回数を少なくできる.

anyComponent.js
export default function AnyComponent() {
  const { data: session } = useSession()
  return <childComponent />
}

AnyComponent.auth = {
  role: "admin",
  loading: <AnyComponentLoadingSkeleton />,
  unauthorized: "/login-with-different-user", // redirect to this url
}
_app.js
export default function App({
  Component,
  pageProps: { session, ...pageProps },
}) {
  return (
    <SessionProvider session={session}>
      {Component.auth.role === "admin" ? (
        <Admin>
          <Component {...pageProps} />
        </Admin>
      ) : (
        <Client>
          <Component {...pageProps} />
        </Client>
      )}
    </SessionProvider>
  )
}

getSession

useSessionのサーバー版です.正確には,React(Next.js)の外側でセッションが欲しいときに使います.

example
async function myFunction() {
  const session = await getSession()
  /* ... */
}

v4.10.3現在,getSessionは非推奨であり,安定版であるgetServerSessionへの移行が進められています.詳しくは unstable_getServerSession を見てください.

getCsrfToken

オリジナルのサインインページを作成するときに使います.後述するビルトインのsingInsignOut関数では自動でCross Site Request Forgery Token(CSRF Token)が付与されますが,自前で/api/auth/signinなどにPOSTする場合はcsrfTokenが必要です.
クライアント,サーバーどちらも使えます.

client
async function myFunction() {
  const csrfToken = await getCsrfToken()
  /* ... */
}
server
import { getCsrfToken } from "next-auth/react"

export default async (req, res) => {
  const csrfToken = await getCsrfToken({ req })
  /* ... */
  res.end()
}

getProviders

自前でサインインページを作っているとき,SNS等でログインさせたいときがあると思います.こんな時にこの関数で[...nextauth].jsに設定したプロバイダーを取得できます.Objectで取得できます.
クライアント,サーバーどちらも使えます.

client
async function myFunction() {
  const providers = await getProviders()
  /* ... */
}
server
import { getProviders } from "next-auth/react"

export default async (req, res) => {
  const providers = await getProviders()
  console.log("Providers", providers)
  res.end()
}
{
  github: {
    id: "github",
    name: "GitHub",
    type: "oauth",
    signinUrl: "http://localhost:3000/api/auth/signin/github",
    callbackUrl: "http://localhost:3000/api/auth/callback/github"
  },
  ...
}

signIn・signOut

NextAuth.jsが標準で提供するサインイン・サインアウト関数です.

example
import { signIn, signOut } from "next-auth/react"

export default () => <button onClick={() => signIn()}>Sign in</button>
// or 
export default () => (
  <button onClick={() => signIn("google")}>Sign in with Google</button>
)
// or 
export default ({ email }) => (
  <button onClick={() => signIn("email", { email })}>Sign in with Email</button>
)

export default () => <button onClick={() => signOut()}>Sign out</button>

また,第2引数に認証後のリダイレクト先を指定することもできます.

example
signIn(undefined, { callbackUrl: 'http://localhost:3000/foo' })
signOut({ callbackUrl: 'http://localhost:3000/foo' })

emailcredentialsでの認証のみにおいてリダイレクトをせずにPromiseを取得できます.

example
const res = signIn('credentials', { redirect: false, password: 'password' })

// resの中身
{
  error: string | undefined
  status: number
  ok: boolean
  url: string | null
}

sessionProvider

前回も説明した通り,ページやコンポーネントの中でセッションを使いたい場合はsessionProviderでラップします.ここで取得したセッションはタブごとに保存され,全てのタブで同期されます.また,localStoragesessionStorageに保存されることはありません.(Cookieに保存されます.)

example
import { SessionProvider } from "next-auth/react"

export default function App({
  Component,
  pageProps: { session, ...pageProps },
}) {
  return (
    <SessionProvider
      session={session}
      // Re-fetch session every 5 minutes
      refetchInterval={5 * 60}
      // Re-fetches session when window is focused
      refetchOnWindowFocus={true}
    >
      <Component {...pageProps} />
    </SessionProvider>
  )
}

このとき,各ページでは以下の挙動と同義です.

example
export async function getServerSideProps(ctx) {
  return {
    props: {
      session: await getSession(ctx)
    }
  }
}

Next.js@12.3.0以降でTypeScriptを使う場合,_app.tsxは以下のように書き換える必要があります.

import type { AppProps } from 'next/app'
import { Session } from 'next-auth'
import { SessionProvider } from "next-auth/react"

export default function App({
  Component,
  pageProps: { session, ...pageProps },
}: AppProps<{ session: Session }>) {
  return (
    <SessionProvider
      session={session}
      // Re-fetch session every 5 minutes
      refetchInterval={5 * 60}
      // Re-fetches session when window is focused
      refetchOnWindowFocus={true}
    >
      <Component {...pageProps} />
    </SessionProvider>
  )
}

https://github.com/vercel/next.js/issues/40371#issuecomment-1241817292


今回はここまで.次回は[...nextauth].jsについて書いていきます.

28
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
7