はじめに
最近,Next.jsを使ったwebアプリケーションを作り始めました.この中でログイン機能を付けたくてNextAuth.jsの導入をしたのですが,無知すぎて何をしているのかわかりませんでした.そこで,勉強を兼ねてここにドキュメントの内容を日本語訳して記しておきます.
2023/06/11 追記
NextAuth.jsはAuth.jsに変わりました.Next.js App Routerの場合の実装も併せて新しく記事を作成したのでそちらをご覧ください.
この記事の内容
この記事はNextAuthのドキュメントを私なりに解釈して書き留めたものです.いくつか誤りがあると思いますのでコメント等で指摘していただくと幸いです.
ドキュメントを読みたい方は以下からどうぞ.
前回の記事は以下からどうぞ.
今回はclient sideについてです.
2022/02/21現在の最新バージョンは4.2.1であり,この記事はその時のドキュメントを元に書いています.
2022/09/25 追記
最新バージョン4.10.3に更新しました.
Sessionの取得
ページやコンポーネント内でセッションを取得するにはuseSession
Hookを使います.
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>
}
useSession
はdata
とstatus
を返します.data
の型は
- fetch前は
undefined
- fetchに失敗した場合は
null
- fetchに成功した場合は
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
を与えることで判定回数を少なくできる.
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
}
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)の外側でセッションが欲しいときに使います.
async function myFunction() {
const session = await getSession()
/* ... */
}
v4.10.3現在,getSession
は非推奨であり,安定版であるgetServerSession
への移行が進められています.詳しくは unstable_getServerSession を見てください.
getCsrfToken
オリジナルのサインインページを作成するときに使います.後述するビルトインのsingIn
やsignOut
関数では自動でCross Site Request Forgery Token(CSRF Token)
が付与されますが,自前で/api/auth/signin
などにPOSTする場合はcsrfTokenが必要です.
クライアント,サーバーどちらも使えます.
async function myFunction() {
const csrfToken = await getCsrfToken()
/* ... */
}
import { getCsrfToken } from "next-auth/react"
export default async (req, res) => {
const csrfToken = await getCsrfToken({ req })
/* ... */
res.end()
}
getProviders
自前でサインインページを作っているとき,SNS等でログインさせたいときがあると思います.こんな時にこの関数で[...nextauth].js
に設定したプロバイダーを取得できます.Objectで取得できます.
クライアント,サーバーどちらも使えます.
async function myFunction() {
const providers = await getProviders()
/* ... */
}
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が標準で提供するサインイン・サインアウト関数です.
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引数に認証後のリダイレクト先を指定することもできます.
signIn(undefined, { callbackUrl: 'http://localhost:3000/foo' })
signOut({ callbackUrl: 'http://localhost:3000/foo' })
email
とcredentials
での認証のみにおいてリダイレクトをせずにPromiseを取得できます.
const res = signIn('credentials', { redirect: false, password: 'password' })
// resの中身
{
error: string | undefined
status: number
ok: boolean
url: string | null
}
sessionProvider
前回も説明した通り,ページやコンポーネントの中でセッションを使いたい場合はsessionProvider
でラップします.ここで取得したセッションはタブごとに保存され,全てのタブで同期されます.また,localStorage
やsessionStorage
に保存されることはありません.(Cookieに保存されます.)
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>
)
}
このとき,各ページでは以下の挙動と同義です.
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
について書いていきます.