🔐

Next Authを使って認証周りをよしなにやってもらった(Google・Github版)

2023/10/26に公開

最近仕事でNextAuthを触って便利だったので、あとで見返せれるように記事でまとめてみました。

まだ細かいところまでは知らないのですが、最低限の認証処理は実装できたので参考になればと思います。

🐈 Next Authとは

NextAuthは、初心者でも簡単な設定でセキュアな認証機能を提供し、多くのプロバイダーに対応しており、ユーザー管理やセッション管理も手軽に行えます。

https://next-auth.js.org/

🚀 セットアップ

ライブラリのインストール

next-authをインストールする

pmpm add next-auth

ファイルの修正

_app.tsxを以下のように修正する

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

export default function App({ Component, pageProps }: AppProps) {
  return (
    <SessionProvider session={pageProps.session}>
      <Component {...pageProps} />
    </SessionProvider>
  );
}

環境変数の設定

NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=

NEXTAUTH_SECRETは以下のコマンドを叩いて取得する

openssl rand -base64 32

セッションの署名やJWTの署名関連の時に必要になるので用意しておきます

使用するプロバイダー

  • Google
  • Github

pages/api/authに[…nextauth].tsファイルを作成し、プロバイダーのモジュールをインポートする

import GoogleProvider from "next-auth/providers/google";
import GithubProvider from "next-auth/providers/github";

📝 コード解説

ログインページの実装

login.tsx
import { signIn } from "next-auth/react";

return(
  {/* Google認証 */}
  <button onClick={()=>signIn("google", { callbackUrl: `${process.env.NEXTAUTH_URL}/user`})}>Googleログイン</button>
  {/* Github認証 */}
  <button onClick={()=>signIn("github", { callbackUrl: `${process.env.NEXTAUTH_URL}/user`})}>Googleログイン</button>
)

signIn()の第1引数にプロバイダー名を設定して、第2引数をcallbackUrlでログイン後にリダイレクトしたいURLを設定する。

callbackUrlを設定することでクリックした際に、よく見る認証画面に飛びます。

Next Auth設定の実装

[…nextauth].tsの実装方法は以下を参考にしてます
https://github.com/calcom/cal.com/blob/main/packages/features/auth/lib/next-auth-options.ts

[…nextauth].ts
import NextAuth, { NextAuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import GithubProvider from "next-auth/providers/github";
import { Provider } from "next-auth/providers/index";

// * 使用するプロバイダーの有効化(環境変数で定義するのがスタンダード)
const IS_GOOGLE_LOGIN_ENABLED = true;
const IS_GITHUB_LOGIN_ENABLED = true;

const providers: Provider[] = [];

if (IS_GOOGLE_LOGIN_ENABLED) {
  providers.push(
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
      authorization: {
        params: {
          scope: "openid email profile",
          access_type: "offline",
        },
      },
    })
  );
}

if (IS_GITHUB_LOGIN_ENABLED) {
  providers.push(
    GithubProvider({
      clientId: process.env.GITHUB_CLIENT_ID as string,
      clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
      authorization: {
        params: {
          scope: "read:user user:email",
        },
      },
    })
  );
}

export const options: NextAuthOptions = {
  secret: process.env.NEXTAUTH_SECRET,
  pages: {
    signIn: "/login",
  },
  providers: providers,
  callbacks: {
    async redirect({ url }) {
      return url;
    },
    async jwt({ token, user, account }) {
      if (user && account) {
        // サインイン時のみ実行
        return {
          ...token,
          access_token: account.access_token,
          refresh_token: account.refresh_token || null,
          id_token: account.id_token || null,
        };
      }

      return token;
    },
    async session({ session, token, user }) {
      return {
        ...session,
        access_token: token.access_token,
        refresh_token: token.refresh_token,
        id_token: token.id_token,
      };
    },
  },
  jwt: {
    secret: process.env.NEXTAUTH_SECRET,
  },
  session: {
    strategy: "jwt",
  },
};

export default NextAuth(options);

Pages

カスタムしたサインインページやサインアウトページを指定することができます。

redirect

signIn()のcallbackURLで定義した値がurlに渡され、その値をreturnします。

jwt

callbacksのjwtはサインイン時と、getSession()getServerSession()useSession()が呼び出された際に実行されるため、その処理を記述しています。

なお、userやaccount情報はサインイン時のみ取得されるため、アクセストークンやリフレッシュトークン、Idトークン等を取得したい場合は、上記のような条件式で実装することで可能です。

※GitHubではリフレッシュトークンおよびIdトークンが存在しないため、nullとなります。

session

jwtセッションを使用している場合、jwtのペイロード(token)の値が渡されるので、jwtで定義したものを受け取れます。

今回の場合だとアクセストークンやIdトークンを取得しています。

ログイン後のユーザーページの実装(サーバー処理のみ)

user
export const getServerSideProps: GetServerSideProps = async (context) => {
  const { req, res } = context;

  const token = await getToken({
    req: req,
    secret: process.env.NEXTAUTH_SECRET,
  });

  if (!token) {
    return {
      redirect: {
        destination: "/login",
        permanent: false,
      },
    };
  }

  const session = await getServerSession(req, res, options);

  if (!session) {
    return {
      redirect: {
        destination: "/login",
        permanent: false,
      },
    };
  }

  return {
    props: {},
  };
};

getServerSidePropsでgetTokenもしくはgetServerSessionを呼び出すことにより、[…nextauth].tsで定義されたユーザー情報を取得できます。

tokenやsessionが取得できない場合は、/loginページにリダイレクトすることでアクセス制御を実装できます。

returnのpropsに、tokenやsessionで取得したユーザー情報を加工し定義することで、クライアント側はそれを受け取り利用できるようになります。

getSession()を使用して同様のデータを取得することも可能ですが、useEffectの使用などが必要になる場合があります。そのため、個人的にはgetServerSession()を使用した方がスマートだと考えています。

参考ドキュメント
https://next-auth.js.org/configuration/options#jwt-helper
https://next-auth.js.org/configuration/nextjs#getserversession

😎 終わり

NextAuthの使いやすさ開発体験が良かったので、今後も採用していきたいなと思いました!

今回使ったプロバイダーはGoogleとGithubですが、他にもAzureADやSlackなど50以上のプロバイダーを使えるのでかなり良きです^^

👀 おまけ

弊社では、スマホやPC1つで完結する網羅的な教材と、無制限で解ける本番と同形式の模試で、短期間での資格取得を目指すことができる簿記のアプリ 『Funda簿記』 を運営しています。

少しでも興味のある方がいれば、リンクよりアクセスしていただくか、メールにてお願いします☺️

https://boki.funda.jp/

Discussion