NextAuth.jsでNext.js13にGoogle認証機能を実装
はじめに
Next.js に NextAuth.js を使って Google アカウントでログイン・ログアウトする機能を実装します。
こちらに実装したサンプルコードを公開しています。
NextAuthとは❓
NextAuth.jsは、Next.js で認証機能を実装するためのライブラリです。
Next.jsプロジェクトを構築
Next.js プロジェクトを構築します。
$ yarn create next-app nextauth_google_provider --typescript --eslint --src-dir --import-alias "@/*"
プロジェクトのディレクトリに移動します。
$ cd nextauth_google_provider
NextAuth.jsをインストール
NextAuth.js をインストールします。
$ yarn add next-auth
Session Providerの設定
<SessionProvider>
を使用することで、useSession()
を通し Component 間で Session 情報を共有できます。src/pages/_app.tsx
に <SessionProvider>
を設定します。
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
import { SessionProvider } from 'next-auth/react'
export default function App({ Component, pageProps:{session, ...pageProps} }: AppProps) {
return (<SessionProvider session={session}><Component {...pageProps} /></SessionProvider>);
}
NEXTAUTH_URLの設定
NextAuth.js を使用するには、NEXTAUTH_URL
を環境変数に設定する必要があります。.env.local
を作成し NEXTAUTH_URL を設定します。
NEXTAUTH_URL=http://localhost:3000
[...nextauth].jsの作成
src/pages/api/auth/[...nextauth].ts
を作成します。src/pages/api/auth/*
への全てのリクエストは NextAuth.js の API が呼び出され処理されます。
ディレクトリを作成します。
$ mkdir -p src/pages/api/auth
import NextAuth, { NextAuthOptions } from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
type ClientType = {
clientId: string;
clientSecret: string;
};
const authOptions: NextAuthOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
} as ClientType),
],
secret: process.env.NEXTAUTH_SECRET,
};
export default NextAuth(authOptions);
以下の 3 つの環境変数を設定する必要がありますが、後ほど設定します。
値 | 説明 |
---|---|
GOOGLE_CLIENT_ID | GoogleのOAuth2.0クライアントID |
GOOGLE_CLIENT_SECRET | GoogleのOAuth2.0クライアントシークレット |
NEXTAUTH_SECRET | シークレット |
ログイン画面の実装
src/pages/login.tsx
にてログイン画面を実装します。セッション情報を useSession()
にて取得します。セッション情報が存在する場合はユーザー情報とログボタンを表示し、セッション情報が存在しない場合はログインボタンを表示します。
import React from 'react';
import { useSession, signIn, signOut } from 'next-auth/react';
import { NextPage } from 'next';
const Login: NextPage = () => {
// sessionには、以下のような値が入っています。
// {
// "user":{
// "name":"John",
// "email":"john@examle.com",
// "image":"https://lh3.googleusercontent.com/a/AGNmyxZF7jQN_YTYVyxIx5kfdo3kalfRktVD17GrZ9n=s96-c"
// },
// "expires":"2023-04-01T00:29:51.016Z"
// }
const { data: session } = useSession();
return (
<>
{
// セッションがある場合、ログアウトを表示
session && (
<div>
<h1>ようこそ, {session.user && session.user.email}</h1>
<button onClick={() => signOut()}>ログアウト</button>
</div>
)
}
{
// セッションがない場合、ログインを表示
// ログインボタンを押すと、ログインページに遷移する
!session && (
<div>
<p>ログインしていません</p>
<button onClick={() => signIn()}>ログイン</button>
</div>
)
}
</>
);
};
export default Login;
ローカル環境で実行
yarn dev
でローカル環境で実行します。
$ yarn dev
http://localhost:3000/login にアクセスするとログインボタンが表示されます。
「Sign in with Google」をクリックして、ログインします。
エラーが表示されます。これは、認証情報が設定されていないためです。
認証情報の取得
NextAuth で Google の OAuth2.0 を使用するには、Google の OAuth2.0 の認証情報(クライアント ID とクライアントシークレット)が必要です。
取得方法の流れは以下の通りです。
- Google Developer Console にアクセスします。
- Google Cloud の利用について同意します。
- 新しいプロジェクトを作成します。
- OAuth の同意画面を作成します。
- アプリケーションを登録し認証情報(クライアント ID とクライアントシークレット)を取得します。
Google Developer Consoleにアクセス
Google Developer Console の認証情報ページにアクセスします。
Google Cloudの利用について同意
はじめて Google Cloud を使う場合のみ、Google Cloud の同意が求められます。
処理詳細
- 利用規約を確認します。
- 同意にチェックを入れます。
新しいプロジェクトの作成
既に作成済みのプロジェクトがある場合は、そのプロジェクトを選択します。
ない場合は、新しいプロジェクトを作成します。
処理詳細
- 「プロジェクトを作成」を押下します。
- 「プロジェクト名」は任意の名前を入力します。
- 「組織なし」のままにします。
- 「作成」を押下します。
OAuthの同意画面の作成
利用者に向けて開示する OAuth の同意画面を作成します。
処理詳細
- 「認証情報の作成」をクリックします。
- 「同意画面を設定」をクリックします。
- 「外部」を選択し、「作成」ボタンを押下します。
- アプリ情報に「アプリ名」「ユーザーサポートメール」、デベロッパー連絡先情報に「メールアドレス」を入力し「保存して次へ」をクリックします。
- 何もせず、「保存して次へ」をクリックします。
- 何もせず、「保存して次へ」をクリックします。
- 内容を確認し問題なければ「ダッシュボードに戻る」をクリックします。
認証情報の取得
認証情報(クライアント ID とクライアントシークレット)を取得します。
処理詳細
- 「認証情報を作成」をクリックします。
- 「OAuth クライアント ID」をクリックします。
- 「アプリケーションの種類」は「ウェブアプリケーション」を選択します。「名前」は任意の名前を入力します。
- 「承認済みの JavaScript 生成元」にhttp://localhost:3000を入力します。
- 「承認済みのリダクレイト URI」にhttp://localhost:3000/api/auth/callback/googleを入力します。
- 「クライアント ID」と「クライアントシークレット」をコピーします。
環境変数の設定
先程コピーした「クライアント ID」と「クライアントシークレット」を環境変数に設定します。
NEXTAUTH_URL=http://localhost:3000
+GOOGLE_CLIENT_ID=263176362010-ut35otqp0qp3oq8676pjnlf359uk0tqf.apps.googleusercontent.com
+GOOGLE_CLIENT_SECRET=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
JWTのシークレットの取得
NextAuth.js は JWT を使用してセッションを管理します。JWT のシークレットは、JWT の署名に使用されます。JWT のシークレットは、ランダムな文字列である必要があります。JWT のシークレットを作成する方法を2つ記載します。
方法1:opensslコマンドを使用する
openssl
コマンドを使用して JWT のシークレットを生成します。
$ openssl rand -base64 32
IHPcQI71tUBPOJ7jxkRhjKRv7Ak5nvnz9xCZEPBeN8U=
方法2:
方法 1 のコマンドが実行出来ない場合、Web アプリケーションで JWT のシークレットを生成できます。
設定
上記のいずれかの方法で取得した 32 桁の文字列をコピーし、環境変数に設定します。
NEXTAUTH_URL=http://localhost:3000
GOOGLE_CLIENT_ID=263176362010-ut35otqp0qp3oq8676pjnlf359uk0tqf.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=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
+NEXTAUTH_SECRET=dbe2de07f88c3a9b60a4503bb1d015a8
動作確認
動作確認します。yarn dev
で開発サーバーを起動します。
$ yarn dev
実行結果
yarn dev
yarn run v1.22.19
warning ../package.json: No license field
$ next dev
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
info - Loaded env from /Users/hayato94087/Work/nextauth-app_/.env.local
ブラウザでhttp://localhost:3000/loginにアクセスします。ログインボタンをクリックします。
「Sign in with Google」をクリックします。
アカウントを選択します。
ログインできました。
認証されたユーザーのみ閲覧できるページを作成
認証されたユーザーのみ閲覧できるページを作成する方法法を 2 つ紹介します。
- 1 つ目は、クライアントサイドで閲覧可能な情報を制御する方法です。
- 2 つ目は、サーバサイドでで閲覧可能な情報を制御する方法です。
クライアントサイドでの閲覧可能な情報の制御
ここでは、セッションを使い、クライアントサイドでの閲覧情報を制御する方法を紹介します。
具体的には以下になります。
-
useSession
でセッションを取得します。 -
useSession
のrequired
オプションをtrue
にし、認証されていない場合は NextAuth のログインページにリダイレクトします。
ソースコード
以下のコードで、クライアントサイドで Session を管理します。
- セッションがある場合は、ユーザーの情報を画面に表示します。
- セッションがない場合は、NextAuth のログイン画面にリダイレクトさせます。
import { signOut, useSession } from 'next-auth/react';
import Image from 'next/image';
import { NextPage } from 'next';
const Profile1: NextPage = () => {
// sessionには、以下のような値が入っています。
// {
// "user":{
// "name":"Taro Yamada",
// "email":"taro@examle.com",
// "image":"https://lh3.googleusercontent.com/a/AGNmyxZF7jQN_YTYVyxIx5kfdo3kalfRktVD17GrZ9n=s96-c"
// },
// "expires":"2023-04-01T00:29:51.016Z"
// }
const { data: session } = useSession({ required: true });
return (
<>
{
// セッションがある場合は、プロファイルを表示する
session && (
<div>
<h1>プロファイル</h1>
<div>{session.user?.email}</div>
{session.user?.image && (
<div>
<Image src={session.user?.image} alt="" width={96} height={96} />
</div>
)}
<button onClick={() => signOut()}>Sign out</button>
</div>
)
}
{
// セッションがない場合は、ログインページに遷移する
!session && (
<div>
<p>You are not signed in.</p>
</div>
)
}
</>
);
};
export default Profile1;
<Image>
コンポーネントを使用するために、next.config.js
に images
の設定を追加します。
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
images: {
domains: ['lh3.googleusercontent.com'],
},
};
module.exports = nextConfig;
ソースコードの補足 : セッション管理
session の有無で表示するコンテンツをクライアントサイドで切り分けています。
const { data: session } = useSession({ required: true });
return (
<>
{
// セッションがある場合は、プロファイルを表示する
session && (
<div>
<h1>プロファイル</h1>
<div>{session.user?.email}</div>
{session.user?.image && (
<div>
<Image src={session.user?.image} alt="" width={96} height={96} />
</div>
)}
<button onClick={() => signOut()}>Sign out</button>
</div>
)
}
{
// セッションがない場合は、ログインページに遷移する
!session && (
<div>
<p>You are not signed in.</p>
</div>
)
}
</>
);
動作確認
サインインしていない状態でhttp://localhost:3000/profile1にアクセスすると、Google アカウントの認証ページにリダイレクトされます。
サインインしている状態でhttp://localhost:3000/profile1にアクセスすると、Google アカウントの情報が見えます。
サーバサイドでの閲覧可能な情報の制御
ここでは、セッションを使い、サーバサイドでの閲覧情報を制御する方法を紹介します。
具体的には以下になります。
-
getServerSession
でセッションを取得します。 - 認証されていない場合は、サーバ側で強制的にログインページにリダイレクトさせます。
ソースコード
以下のコードで、サーバサイドで Session を管理します。
- セッションがある場合は、ユーザーの情報を画面に表示します。
- セッションがない場合は、ログイン画面にリダイレクトさせます。
import { signOut } from 'next-auth/react';
import { GetServerSideProps } from 'next';
import Image from 'next/image';
import { NextPage } from 'next';
import { getServerSession } from 'next-auth/next';
import { authOptions } from '@/pages/api/auth/[...nextauth]';
export const getServerSideProps: GetServerSideProps = async (context) => {
// sessionには、以下のような値が入っています。
// {
// "user":{
// "name":"Taro Yamada",
// "email":"taro@examle.com",
// "image":"https://lh3.googleusercontent.com/a/AGNmyxZF7jQN_YTYVyxIx5kfdo3kalfRktVD17GrZ9n=s96-c"
// },
// "expires":"2023-04-01T00:29:51.016Z"
// }
//
const session = await getServerSession(context.req, context.res, authOptions);
// 以下のように、getSessionを使うこともできますが、非推奨です。
// https://next-auth.js.org/configuration/nextjs#unstable_getserversession
// const session = await getSession(context);
if (!session) {
return {
redirect: {
destination: '/login',
permanent: false,
},
};
}
// session データを用いてサーバサイドで処理を行います.
// 例えば、userに紐づくデータをAPI経由で取得するなどの処理も可能です。
// ここでは、何かしらAPIを呼び、ユーザーの誕生日を取得したと仮定し、birthdayを追加しています。
const userItem = {
name: session.user?.name,
email: session.user?.email,
image: session.user?.image,
birthday: '2021-01-01',
};
return {
props: userItem,
};
};
// props type
type Props = {
name: string;
email: string;
image: string;
birthday: string;
};
// propsには、以下のような値が入っています。
// {
// "name":"Taro Yamada",
// "email":"taro@examle.com",
// "image":"https://lh3.googleusercontent.com/a/AGNmyxZF7jQN_YTYVyxIx5kfdo3kalfRktVD17GrZ9n=s96-c"
// "birthday":"2021-01-01"
// }
const Profile2: NextPage<Props> = (props) => {
const { name, email, image, birthday } = props;
return (
<>
<div>
<h1>プロファイル</h1>
<div>{name}</div>
<div>{birthday}</div>
<div>{email}</div>
<div>
<Image src={image} alt="" width={96} height={96} />
</div>
<button onClick={() => signOut()}>Sign out</button>
</div>
</>
);
};
export default Profile2;
補足①︰getSessionについて
公式に記載の通り、getSession
は使わずに、getServerSession
を使用してセッション情報を取得します。
const session = await getServerSession(context.req, context.res, authOptions);
補足②︰getServerSidePropsについて
getServerSideProps
は、Next.js の機能で、サーバサイドで処理を行うための関数です。
getServerSideProps
を使用することで、サーバサイドでセッション情報を取得し、画面に表示できます。
例えば、セッションがない場合はログイン画面に強制的にリダイレクトさせることができます。
if (!session) {
return {
redirect: {
destination: '/login',
permanent: false,
},
};
}
補足③︰props
getServerSideProps
で取得したセッション情報を、元になにかした処理を行い、クライアントサイドで表示するために、props
として渡せます。
例えば、認証済みのユーザーに紐づくデータを API 経由で取得し、props で渡すことも可能です。
return {
props: userItem,
};
動作確認
サインインしていない状態でhttp://localhost:3000/profile2にアクセスすると、Google アカウントの認証ページにリダイレクトされます。
サインインしている状態でhttp://localhost:3000/profile2にアクセスすると、Google アカウントの情報が見えます。
APIの保護
NextAuth を利用し API を保護する方法を説明します。
getServerSession
を利用し、セッション情報を取得することで、セッションの有無に合わせて API を保護できます。
保護されたAPIの作成
以下の API はセッションがない場合は、401 エラーを返します。
import { authOptions } from '@/pages/api/auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
import type { NextApiRequest, NextApiResponse } from 'next';
type Data = {
message: string;
};
const handler = async (req: NextApiRequest, res: NextApiResponse<Data>) => {
const session = await getServerSession(req, res, authOptions);
console.log(session);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
}
return res.json({
message: 'Success',
});
};
export default handler;
動作確認
セッションがない場合は、You must be logged in.というメッセージが表示されます。
セッションがある場合は、Success というメッセージが表示されます。
認証情報の削除
最後に作成した認証情報を削除します。
Google Developer Console の認証情報ページにアクセスします。ゴミ箱の削除ボタンをクリックします。
「削除」をクリックします。
削除が完了しました。
まとめ
- NextAuth.js を利用し Google アカウントでログイン・ログアウトする機能を実装しました。
- ログイン画面、認証されたユーザーのみ閲覧できる画面を作成しました。
- ユーザー認証を利用した API を作成しました。
参考
NextAuth.js の公式サイトです。
NextAuth.js を使った Google 認証機能を実装している動画です。これを見れば実装が出来ます。
Goole 認証に関する NextAuth.js の公式ドキュメントです。
NextAuth.js の公式サンプルコードです。
Discussion