🙌
【Next.js・Typescript】NexAuthを使ってログイン認証をする
Zennは初体験、ReactもNext.jsもまだ経験1ヶ月、NextAuthはもちろん手探り・・
ほぼ自分のためのメモです・・用語などおかしな点があるかとおもいます・・
目的
NextAuthを使って、以下のような普通のログイン認証画面を実装したい。
環境
- Next.js
- ユーザ情報はDB参照(APIで取得)
- Material-UI(MUI)
- React Hook Form
Session Providerの設定
NextAuth.jsではSessionにユーザ情報を保存する。
Sessionの内容をアプリケーションから確認できるようにするには、SessionProviderの設定が必要となる。
pages/_app.tsx にSessionProviderを設定する
_app.tsx
import { SessionProvider } from 'next-auth/react';
function MyApp({ Component, pageProps: { session, ...pageProps } }) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
);
}
export default MyApp;
useSessionでSessionにアクセスする
NextAuthの「useSession」にて、コンポーネントからsessionへのアクセスが可能となる。
以下がその例
ExampleComponent.tsx
const ExampleComponent = () => {
const { data: session } = useSession();
return (
<div>
{ session && (
・・・
)}
</div>
);
NextAuthの設定
[...nextauth].ts
pages/api/auth配下に、[...nextauth].tsファイルを作成し、next-authの設定をしていく
[...nextauth].ts
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { postSigninUser } from "../../../api/loginuser";
export default NextAuth({
providers: [
CredentialsProvider({
id: "credentials",
name: "credentials",
credentials: {
usercode: {
label: "User ID",
type: "text",
placeholder: "User ID",
},
password: { label: "Password", type: "password" },
},
authorize: async (credentials, req) => {
const postData = {
user_code: credentials?.usercode,
password: credentials?.password,
};
// APIのpostにて、ユーザーテーブルからログインユーザデータを取得してくる
// postSigninUserメソッドは別途定義している(axiosを使用)
const res = await postSigninUser(postData);
if (res.message === "no data") {
// throw new Error(res);
return null;
} else {
const user = {
name: res.data[0].shimei,
email: res.data[0].user_code,
};
return user;
}
},
}),
],
callbacks: {
async jwt({ token, user, account }) {
// 最初のサインイン
if (account && user) {
return {
...token,
accessToken: user.token,
refreshToken: user.refreshToken,
};
}
return token;
},
async session({ session, token }) {
session.accessToken = token.accessToken;
session.refreshToken = token.refreshToken;
session.accessTokenExpires = token.accessTokenExpires;
return session;
},
},
secret: process.env.NEXTAUTH_SECRET,
// サインイン・サインアウトで飛ぶカスタムログインページを指定
// サインアウト時に、”Are you sure you want to sign out?”と聞かれるページを挟むのをスキップする
pages: {
signIn: "/login",
signOut: "/login",
},
// Enable debug messages in the console if you are having problems
debug: process.env.NODE_ENV === "development",
});
環境変数(開発環境では.env
)に以下を設定
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=XXXX
XXXの部分は、以下コマンドで出てくるキーを記述する
$ openssl rand -base64 32
カスタムログイン画面
react-hook-formを利用、classNameでcssファイルを使ったstyle設定は削っています。
login.tsx
import { useState } from "react";
import { Button } from "@mui/material";
import { ReactElement } from "react";
import { getCsrfToken, signIn } from "next-auth/react";
import { useRouter } from "next/router";
import { CtxOrReq } from "next-auth/client/_utils";
import { useForm } from "react-hook-form";
var md5 = require("md5");
// POSTリクエスト(サインイン・サインアウトなど)に必要なCSRFトークンを返却する
export const getServerSideProps = async (context: CtxOrReq | undefined) => {
return {
props: {
title: "login",
csrfToken: await getCsrfToken(context),
},
};
};
interface IFormValues {
user_code?: string;
password?: string;
}
const Login = ({ csrfToken }: { csrfToken: string | undefined }) => {
const router = useRouter();
const [error, setError] = useState("");
const { register, handleSubmit } = useForm<IFormValues>();
const signInUser = async (data: IFormValues) => {
// ここで<any>を書かないとtypeエラーが消えなかったので書いています
await signIn<any>("credentials", {
redirect: false,
usercode: data.user_code,
password: data.password,
callbackUrl: `${window.location.origin}`,
}).then((res) => {
if (res?.error) {
setError("UserId,Passwordを正しく入力してください");
} else {
// ログイン後に飛ぶページ
router.push("/Xxxxxx");
}
});
};
return (
<div style={{ textAlign: "center" }}>
<form onSubmit={handleSubmit(signInUser)}>
<input name="csrfToken" type="hidden" defaultValue={csrfToken} />
<div style={{ marginTop: "15px" }}>
<input
type="text"
placeholder="User ID"
{...register("user_code")}
></input>
</div>
<div>
<label htmlFor="password"></label>
<input
type="password"
placeholder="Password"
{...register("password", {
setValueAs: (value) => md5(value),
})}
></input>
</div>
<p>
<span style={{ color: "red" }}>{error}</span>
</p>
<div>
<Button
variant="contained"
sx={{
width: "100%",
}}
type="submit"
>
ログイン
</Button>
</div>
</form>
</div>
);
};
export default Login;
session ログインしていない場合
ログイン画面に飛ばす。
_app.tsxのuseEffectに記述する。
_app.tsx
function MyApp({
Component,
pageProps: { session, ...pageProps },
router,
}: AppPropsWithLayout) {
useEffect(() => {
// ここに全ページ共通で行う処理
router.push("/login");
}, []);
ハマったこと
実装中にハマったこと、実装中のメモをスクラップに書いています。
参考にさせていただいたサイト
これ見ながらやればひととおり実装できる
結局このサイトをかなり参考にした
SessionProvider、session,callbacksなどの説明が詳しい
Discussion