NextAuth.js +TypeScript 素振り
はじめに
こんにちは、かべです。少し前に Firebase を使った認証ページを作ったので、その続編として今度は NextAuth.js を使った認証ページを作っていこうと思います。公式のチュートリアルから飛べる動画でコーディングしながら解説してくれています。英語がかなり聞きやすいのでおすすめです。ただ JavaScript で実装をしていたため、TypeScript に置き換えて作っていきます。
今回は動画内で紹介されているログイン方法の内 GitHub 認証とメールを使った認証について書いていきます。その他のログイン方法についてもこれと大差ありません。
環境
今回の開発は以下の環境で行いました。
- Ubuntu 20.04.1
- NextAuth 3.14.8
- yarn 1.22.5
- Next 10.1.3
- React 17.0.2
- TypeScript 4.0
Next アプリの作成、事前準備
まず、例のごとくアプリを作成していきます。npx create-next-app --example with-typescript ${好きな名前}
でさくっと TypeScript の入った Next のアプリを作り、いらない部分を全て消します。完成したらそこへ移動し、yarn add next-auth sqlite3
と yarn add -D @types/next-auth
を実行します。sqlite3 の部分はどんなデータどんなでも良いですが、今回は動画に合わせます。
また、メール認証のために SendGrid のアカウントが必要なので、無ければアカウントを作成します。登録に審査があり1営業日程度かかるらしいので、急ぎの方は注意です。
NextAuth を使用する準備
環境変数の設定
GitHub 連携用の OAuth App を作成します。GitHub の Settings / Developer settings / OAuth Apps
にアクセスし、New OAuth App からアプリを作成します。作成出来たら Client ID と Client secrets を控えておきましょう。
次に、SendGrid の設定を行います。SendGrid のアカウントを作成したら、Email API の中の SMTP Relay を選択します。適当な API Key の名前を決めて Key を作成し、Server、Ports、Username、Password を控えておきましょう。
これらの値が取得出来たら、環境変数に設定します。
.env.local
GITHUB_ID='******' GITHUB_SECRET='******' EMAIL_SERVER_USER='apikey' EMAIL_SERVER_PASSWORD='******' EMAIL_SERVER_HOST='smtp.sendgrid.net' EMAIL_SERVER_PORT='587' EMAIL_FROM='***@***' NEXTAUTH_URL='http://localhost:3000' DATABASE_URL='sqlite://localhost/:memory:'
/next.config.js
module.exports = { env: { GITHUB_ID: process.env.GITHUB_ID, GITHUB_SECRET: process.env.GITHUB_SECRET, AUTH0_CLIENTID: process.env.AUTH0_CLIENTID, AUTH0_CLIENT_SECRET: process.env.AUTH0_CLIENT_SECRET, AUTH0_DOMAIN: process.env.AUTH0_DOMAIN, EMAIL_SERVER_USER: process.env.EMAIL_SERVER_USER, EMAIL_SERVER_PASSWORD: process.env.EMAIL_SERVER_PASSWORD, EMAIL_SERVER_HOST: process.env.EMAIL_SERVER_HOST, EMAIL_SERVER_PORT: process.env.EMAIL_SERVER_PORT, EMAIL_FROM: process.env.EMAIL_FROM, NEXTAUTH_URL: process.env.NEXTAUTH_URL, DATABASE_URL: process.env.DATABASE_URL, }, };
[...nextauth].ts
の作成
ログインの操作をした時に使用する認証の種類やその設定などを記述するファイルを書きます。これは /pages/api/auth/[...nextauth].ts
に作成します。
/pages/api/auth/[...nextauth].ts
import { NextApiRequest, NextApiResponse } from "next"; import NextAuth from "next-auth"; import Providers from "next-auth/providers"; const options = { providers: [ Providers.GitHub({ clientId: process.env.GITHUB_ID as string, clientSecret: process.env.GITHUB_SECRET as string, }), Providers.Email({ server: { host: process.env.EMAIL_SERVER_HOST as string, port: Number(process.env.EMAIL_SERVER_PORT), auth: { user: process.env.EMAIL_SERVER_USER as string, pass: process.env.EMAIL_SERVER_PASSWORD as string, }, }, from: process.env.EMAIL_FROM, }), ], database: { type: "sqlite", database: ":memory:", synchronize: true, }, }; export default (req: NextApiRequest, res: NextApiResponse) => NextAuth(req, res, options);
clientId
などの型が string
で string | undefined
が認められなかったため楽するために as
使ってますが許してください。今回は GitHub とメールしか使っていませんが、その他の認証を使いたいときは同様に providers
の中に好きなものを足せばよいです。
_app.tsx
の作成
アプリ全体でセッションを使う準備をします。/pages/_app.tsx
にセッションの Provider を用意し、アプリ全体を囲ってあげます。
/pages/_app.tsx
import { AppProps } from "next/app"; import { Provider } from "next-auth/client"; const MyApp = ({ Component, pageProps }: AppProps) => { return ( <Provider session={pageProps.session}> <Component {...pageProps} /> </Provider> ); }; export default MyApp;
ここまで出来たら、いよいよアプリの中身を作成していきます。
トップページ
トップページは以下のような機能にします。
- ログインしていない場合、「ログインしてません」という文とログインボタンを表示
- ログインしている場合ユーザーの名前かメールを表示し、ログインした人のみ見られるコンテンツへのリンクを載せる。またログアウトボタンも実装。
これを実現するため、NextAuth の signIn, signOut, useSession
の3つの機能を使います。useSession
で <Provider></Provider>
内のセッションを取得できます。
/pages/index.tsx
import { NextPage } from "next"; import Link from "next/link"; import { signIn, signOut, useSession } from "next-auth/client"; const IndexPage: NextPage = () => { const [session, loading] = useSession(); return ( <> {!session && ( <> <div>ログインしていません</div> <button onClick={() => signIn()}>ログイン</button> </> )} {session && ( <> こんにちは、{session.user.name ?? session.user.email} さん <br /> <button> <Link href="/secret">秘密のページへ</Link> </button> <button onClick={() => signOut()}>ログアウト</button> </> )} </> ); }; export default IndexPage;
Session.user.***
でログイン中のユーザーの情報を取得できます。
ログインした人のみ見られるページ
最後に、ログインした人のみ見られる秘密のページを作ります。まず、ログインしたユーザーのみ叩いて情報を得られる API を作ります。以下はログインしていればステータスコード200 と欲しい情報、ログインしていなければステータスコード 401 を返す API です。TypeScript 用に少し動画と書き方を変えています。
/pages/api/secret/index.ts
import { IncomingMessage, ServerResponse } from "http"; import { getSession } from "next-auth/client"; export default async (req: IncomingMessage, res: ServerResponse) => { const session = await getSession({ req }); if (session) { res.statusCode = 200; res.setHeader("Content-type", "application/json"); res.end( JSON.stringify({ content: "秘密のページへようこそ!!", }) ); } else { res.statusCode = 401; res.setHeader("Content-type", "application/json"); res.end( JSON.stringify({ error: "ログインしてください", }) ); } };
最後に秘密のページを作ります。これでログインしている時は API からの情報を表示し、ログインしていない時は「ログインしてください」という情報を表示することが出来ます。セッションが変化することで useEffect
が走り、情報を取得してきます。セッション管理に使うのは先ほどと同じく useSession
です。
import { NextPage } from "next"; import { useState, useEffect } from "react"; import { useSession } from "next-auth/client"; const Secret: NextPage = () => { const [session, loading] = useSession(); const [content, setContent] = useState(""); useEffect(() => { const fetchData = async () => { const res = await fetch("/api/secret"); const json = await res.json(); if (json.content) { setContent(json.content); } }; fetchData(); }, [session]); if (typeof window !== "undefined" && loading) return <></>; if (!session) { return ( <> <h1>ログインしてください</h1> </> ); } return ( <> <h1>秘密のページ</h1> {content} </> ); }; export default Secret;
これで画像のようなアプリが作れます。
終わりに
SendGrid や(今回はしてませんが) Auth0 を用いた本格的な認証も簡単に作ることができ、非常に便利でした。Firebase を使った認証も以前やったので、そろそろログインを組み込んで何か作りたい…
今回のコードはこちらに上がっています。