Open8

Auth.jsの導入

はらまきはらまき

プロジェクト作成

  • Next.jsのプロジェクトを作成する
    • npx create-next-app@latest
    • プロジェクト名はnext-auth-appにする

インストール

  • Auth.jsはNext.js以外もサポートしているようだが、まずはNext.jsから始める
  • https://authjs.dev/getting-started/installation
  • npmでプロジェクトにインストールすれば良さそう
    • npm install next-auth@beta
    • 2024/9/26時点ではまだbeta版らしい
    • なお、現時点のNext.jsとauth.jsのバージョンは以下のとおり
      • "next": "14.2.13"
      • "next-auth": "^5.0.0-beta.21"
  • 続いて、ターミナルからAUTH_SECRET(秘密鍵)を生成してやる必要があるようだ
    • npx auth secret
    • ルート直下に.env.localファイルが自動生成される
    • .env.local
      AUTH_SECRET="1B73n3qYFSz0u5VhDYO1dVlJg/+ipc4eYabQRB0dUFY="
      
  • 設定
    • 次にルート直下に./auth.tsを追加する
    • ./auth.ts
      import NextAuth from "next-auth"
      export const { handlers, signIn, signOut, auth } = NextAuth({
          providers: [],
        })
      
    • ここで使用する認証サービスを指定するっぽい。詳細は後述
      • Googel
      • GitHub
      • その他...
    • その次にルーティングを用意する→/app/api/auth/[...nextauth]/route.ts
    • /app/api/auth/[...nextauth]/route.ts
      import { handlers } from "@/auth" // Referring to the auth.ts we just created
      export const { GET, POST } = handlers
      
    • セッションを維持するためのオプションのミドルウェアを追加する。
    • 呼び出されるたびにセッションの有効期限を更新するらしい。
    • middleware.ts
        export { auth as middleware } from "@/auth"
      
はらまきはらまき

認証

  • https://authjs.dev/getting-started/authentication
  • Auth.jsは4つの認証方法をサポートしているらしい
    • OAuth
      • Auth.jsには80以上のプロバイダーがあらかじめ設定されています。 私たちは、最も人気のある20のプロバイダーを常にテストしています。 以下からプロバイダーを選んでウォークスルーを見ることもできますし、サイドバーからお好みのプロバイダーを見つけて詳細を見ることもできます。

    • Magic Links
      • このログイン・メカニズムは、ユーザーがログイン・フォームにEメールアドレスを入力することから始まる。 その後、提供されたEメールアドレスにVerification Tokenが送信される。 その後、ユーザーは24時間以内にメール本文にあるリンクをクリックしてトークンを「消費」し、アカウントを登録しなければならない。

    • Credentials
      • Auth.jsを外部認証メカニズムでセットアップしたり、単にユーザー名とパスワードを使用するには、Credentialsプロバイダを使用する必要があります。 このプロバイダは、ログインフォームに挿入された認証情報(ユーザー名/パスワードなど)を、プロバイダ設定のauthorizeコールバックを介して認証サービスに転送するように設計されています。

    • WebAuthn
      • WebAuthn プロバイダーは、すべてのフレームワーク・インテグレーションと、それをサポートする予定のデータベース・アダプターを変更する必要があります。 そのため、WebAuthn プロバイダは現在、以下のフレームワーク・インテグレーションとデータベース・アダプタでのみサポートされています。 より多くのフレームワークとアダプタのサポートは近日中に予定されています。

  • とりあえず、OAuthとCredentialsの使い方を理解しておけばよさそう
はらまきはらまき

OAuth

GitHub

GitHub側の設定

  • まずはGitHub の開発者ダッシュボードで OAuth アプリケーションをセットアップする必要があるみたい。
  • 詳細は公式を参照するべし -> https://authjs.dev/guides/configuring-github
  • GitHub側での設定が終わったら、.env.localを編集する。先程ターミナルで生成したAUTH_SECRETとは別に以下を追加する
  • .env.local
    AUTH_GITHUB_ID={CLIENT_ID}
    AUTH_GITHUB_SECRET={CLIENT_SECRET}
    

プロバイダーの設定

  • GitHub プロバイダをパッケージからインポートし、Auth.js 設定ファイルで先ほど設定した providers 配列に渡す
    • auth.ts
      import NextAuth from "next-auth"
      import GitHub from "next-auth/providers/github"
      export const { handlers, signIn, signOut, auth } = NextAuth({
          providers: [GitHub],
      })
      
  • NextAuthが返すハンドラをapi/auth/[...nextauth]/route.tsファイルに追加し、Auth.jsがリクエストに対して実行できるようにする。
    • api/auth/[...nextauth]/route.ts
      import { handlers } from "@/auth"
      export const { GET, POST } = handlers
      
はらまきはらまき

コンポーネントの作成

  • Navbarのようなアプリケーションのどこかにサインインボタンを追加する。 このボタンをクリックすると、Auth.jsのサインインが開始される。

サーバーコンポーネント

./components/sign-in.tsx

import { signIn } from "@/auth"
 
export default function SignIn() {
  return (
    <form
      action={async () => {
        "use server"
        await signIn("github")
      }}
    >
      <button type="submit">Signin with GitHub</button>
    </form>
  )
}   

クライアントコンポーネント

./components/auth/signin-button.tsx
"use client"
import { signIn } from "next-auth/react"
 
export function SignIn() {
  return <button onClick={() => signIn()}>Sign In</button>
}
はらまきはらまき

認証

  • https://authjs.dev/getting-started/session-management/login
  • 認証されると、ユーザーはサインインを開始したページにリダイレクトされる。
  • サインイン後、ユーザーを他の場所(例:/dashboard)にリダイレクトさせたい場合は、サインインオプションのredirectToにターゲットURLを渡すことで可能。
app/components/signin-button.tsx
import { signIn } from "@/auth.ts"
 
export function SignIn() {
  return (
    <form
      action={async () => {
        "use server"
        await signIn("github", { redirectTo: "/dashboard" })
      }}
    >
      <button type="submit">Sign in</button>
    </form>
  )
}
はらまきはらまき

セッションの利用

サーバーコンポーネントでの利用

  • サーバーコンポーネント内でセッション情報を取得するには、auth()関数を使用する。
./components/UserAvatar.tsx
import { auth } from "../auth"
 
export default async function UserAvatar() {
  const session = await auth()
 
  if (!session.user) return null
 
  return (
    <div>
      <img src={session.user.image} alt="User Avatar" />
    </div>
  )
}

クライアントコンポーネントでの利用

  • クライアントサイドでは、useSessionフックまたはSessionProviderを使用してセッション情報にアクセスできるらしい。
  • ただ実際は、これらの使用頻度はそれほど高くなく、 一般的には、パフォーマンスとセキュリティを最適化するために、サーバー側のレンダリングをフルに活用することが多いらしい。
app/admin/dashboard.tsx
"use client"
import { useSession } from "next-auth/react"
 
export default function Dashboard() {
  const { data: session } = useSession()
 
  if (session?.user?.role === "admin") {
    return <p>You are an admin, welcome!</p>
  }
 
  return <p>You are not authorized to view this page!</p>
}
app/admin/page.tsx
import { SessionProvider } from "next-auth/react"
import { Dashboard } from "./Dashboard"
 
export default function Administrator() {
  return (
    <SessionProvider>
      <Dashboard />
    </SessionProvider>
  )
}
はらまきはらまき

リソースの保護

  • https://authjs.dev/getting-started/session-management/protecting
  • 一般的に、セッションをチェックし、アクティブなセッションが見つからない場合、ユーザをログインページにリダイレクトするか、単に401: Unauthenticatedレスポンスを返すようなアクションを取ることで、経路を保護することができる。
Pages, API Routesは省略

Pages

サーバーコンポーネント

app/server/page.tsx
import { auth } from "@/auth"
 
export default async function Page() {
  const session = await auth()
  if (!session) return <div>Not authenticated</div>
 
  return (
    <div>
      <pre>{JSON.stringify(session, null, 2)}</pre>
    </div>
  )
}

クライアントコンポーネント

import { auth } from "../auth"
 
export default function Dashboard({ session }) {
  if (!session.user) return <div>Not authenticated</div>
 
  return <div>{JSON.stringify(session, null, 2)}</div>
}
 
export async function getServerSideProps(ctx) {
  const session = await auth(ctx)
 
  return {
    props: {
      session,
    },
  }
}
import type { AppProps } from "next/app"
import { SessionProvider } from "next-auth/react"
 
export default function MyApp({
  Component,
  pageProps: { session, ...pageProps },
}: AppProps) {
  return (
    <SessionProvider session={session}>
      <Component {...pageProps} />;
    </SessionProvider>
  )
}

API Routes

  • Next.jsでは、auth関数を使ってAPIルートハンドラをラップすることができます。
  • リクエストパラメータには、有効なセッションをチェックするための認証キーが設定されます。

サーバーコンポーネント

./app/api/admin/route.ts
import { auth } from "@/auth"
import { NextResponse } from "next/server"
 
export const GET = auth(function GET(req) {
  if (req.auth) return NextResponse.json(req.auth)
  return NextResponse.json({ message: "Not authenticated" }, { status: 401 })
})

クライアントコンポーネント

./pages/api/admin.ts
// TODO: Update once server-side API methods are implemented for pages router again
 
// import { auth } from "../../auth"
// import { getSession } from "next-auth/react"
import { NextApiRequest, NextApiResponse } from "next"
 
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  // const session = await auth(req, res)
  // const session = await getSession(req, res)
  const url = `${req.headers["x-forwarded-proto"]}://${req.headers.host}/api/auth/session`
 
  const sessionRes = await fetch(url)
  const session = await sessionRes.json()
 
  if (!session.user) {
    return res.status(401).json({ message: "Not authenticated" })
  }
 
  return res.json({ data: "Protected data" })
}

Next.js Middleware

  • Next.js 12+では、ミドルウェアを使うのがもっとも簡単です。 middleware.tsは、root pagesディレクトリに次のような内容で作成します。
middleware.ts
export { auth as middleware } from "@/auth"
auth.ts
import NextAuth from "next-auth"
 
export const { auth, handlers } = NextAuth({
  callbacks: {
    authorized: async ({ auth }) => {
      // Logged in users are authenticated, otherwise redirect to login page
      return !!auth
    },
  },
})
  • ミドルウェアの内部でさらにロジックを実装したい場合は、authメソッドをラッパーとして使うこともできる。
middleware.ts
import { auth } from "@/auth"
 
export default auth((req) => {
  if (!req.auth && req.nextUrl.pathname !== "/login") {
    const newUrl = new URL("/login", req.nextUrl.origin)
    return Response.redirect(newUrl)
  }
})
  • また、正規表現を使用して複数のルートにマッチさせたり、特定のルートを否定して残りのすべてのルートを保護することもできます。 次の例では、ファビコンや静的画像などのパスでミドルウェアを実行することを避けています。
middleware.ts
export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
}