Passportを使ってUser Login機能を追加する(準備編)
User Login画面を追加する方法を勉強している。
理解できていない部分だらけでどうしようもない状況だが、とりあえずこのブログを書きながら、教わったことや調べたことをメモしていく。
まず、今回やりたいことは普通のシンプルなユーザー登録とユーザーログインの機能を追加すること。ユーザーの認証機能にはPassportを使用する。他の選択肢としてはFirebase AuthenticationやAuth0などがあるが、すでに使っているExpressとの相性がよく、昔からある「定番」で、Google認証などを追加できたりする柔軟性を兼ね備えているということなので採用した。
Passportについて調べていると、Passport.jsはNode.js向けの認証ミドルウェアであるとの説明を目にするが、そもそもミドルウェアとは何か?
ミドルウェア:Web開発において、特にバックエンド(Node.jsやExpressなど)で、HTTPリクエストとレスポンスの間に、認証、エラーハンドリング、ルートの振り分けなどの処理を挟むために使用されるコードを指す。
ちなみに、ルートの振り分けということだとReact Routerもミドルウェアなのか聞いてみたところ、それは違うらしい。React Routerはフロントエンドのライブラリで、クライアントサイドのルーティング(画面遷移やURL管理)を担当する。ではミドルウェアが担う「ルートの振り分け」というのは何なのか?それはリクエストがどのエンドポイント(ルート)で処理されるべきかを決定することを指す。具体的にはExpressがリクエストされたURL(/
login)に来た場合、POSTメソッドに対して指定された処理を選んで実行する仕組みを指すとのことだった。
では本題に入ろう。
Passport.js (https://www.passportjs.org/)
とりあえずまずはインストールしてみる。
npm install passport
npm install passport-local
1行目はPassport自体をインストールしている。2行目のpassport-localは今回使うStrategyをインストールしている。
PassportにはStrategyという認証パターンがたくさん用意されていて、その中の1つであるpassport-localはusername と passwordによる認証パターン。今後Google認証など、認証方法を追加したいときは、他のStrategyも追加していくことになる。
あとそれぞれTypeScript用の”@types/passport”、”@types/passport-local”もインストールしておく。

ちなみに、参考にしていたYouTubeでは、EJSもインストールしていた。私もインストールしたほうがいいものなのか確認すると、Reactを使っているなら必要ないとのこと。
EJS:Reactのような複雑なフロントエンドフレームワークを導入せずに、HTMLページを動的にレンダリングするために使用される。
見ていたチュートリアル動画ではPassport.jsの機能に焦点を当てたシンプルな例を保つためにReactではなくEJSを使っていただけみたい。ここまですでにReactとTypeScriptでWebアプリを作り始めているので、チュートリアル動画通りには行かなくなってしまうが、まぁ仕方ない。
他の人はどうか分からないが、バックエンドで迷子になると、フロント側から作って、どこで行き詰まるのか確認していくほうが理解しやいと感じることがある。
今回もまずはログイン画面を作った。ここを起点にどのようにコード同士が繋がっていくのかを追っていくと、何のためのコードかを捉えやすくなる。

まずはこのページのフロント側でMutationを設定する。以下のコードは抜粋だが、17行目移行でuserLoginMutation.mutate()に、2行目のmutationFnに書いたuserLoginというfunctionを実行するときに渡すパラメータを入れる。mutationFnのuserLoginで何をしているかへジャンプ!
const userLoginMutation = useMutation({
mutationFn: userLogin,
onSuccess: () => {
queryClient.invalidateQueries(["all-challenges"]);
queryClient.invalidateQueries(["user-challenges"]);
navigate(`/`);
},
onError: (error: any) => {
console.error("Mutation error:", error);
},
});
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const data = new FormData(event.currentTarget);
userLoginMutation.mutate({
username: data.get("email-or-username")?.toString() ?? "",
password: data.get("password")?.toString() ?? "",
});
};
userLogin()では、usernameとpasswordをパラメータに受け取り、fetchのbodyに渡している。ここではfetch(resource, options)として2つのパラメータを受け取っているが、このresourceの部分でつまづいたのでメモしておく。
export type userLoginApiRequest = {
username: string;
password: string;
};
export const userLogin = async (user: userLoginApiRequest) => {
const response = await fetch(`/api/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(user),
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return (await response.json()) as getUserApiResponse;
};
vite.config.tsファイルの設定
まず、fetchの中にある/api/auth/loginという部分は、このあとバックエンド側のRepositoryにある以下のコードと繋がる。”/api/auth/login”のmethodがPOSTの時、その先のhandleUserLoginが呼ばれる。
const app = express();
app.use(express.json());
app.post("/api/auth/login", handleUserLogin);
ただ、”/api/auth/login”を”/auth/login”としていたところ、うまくいかなかった。そしてその原因はViteにあった。フロント側の開発ツールでViteをインストールしているが、このvite.config.tsファイルには以下のように設定していた。
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
proxy: {
"/api": {
target: "http://localhost:3000",
changeOrigin: true
}
}
}
});
修正方法としては2つ。
- “/auth/login”を使いたいのであれば、以下例のようにproxyに/authも追加する
- fetchやapp.postのパラメーターを/apiで始まるように”/api/auth/login”とする
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
proxy: {
"/api": {
target: "http://localhost:3000",
changeOrigin: true,
},
"/auth": {
target: "http://localhost:3000",
changeOrigin: true
}
},
},
});
さらに理解を深めるためのメモ:
ViteはdevDependenciesにある通り、開発で使うツール。vite.config.tsファイルでproxyを設定しているが、そもそもこれは何なのか?
フロントエンド開発時、以下のような構成になる
- フロントエンド:
開発サーバー(Vite)が動作するhttp://localhost:5173
- バックエンド:
API サーバー(Node.js/Express)が動作するhttp://localhost:3000
通常、ブラウザはセキュリティ上の理由から、異なるオリジン(ポート番号やドメイン)へのリクエストを制限している。そのため、フロントエンド(localhost:5173
)で実行されたfetch('/api/auth/login')
はバックエンド(http://localhost:3000/api/auth
/user
)に送られることを期待しても、ViteのConfigでProxyを設定しないとオリジンが異なるためエラー(CORSエラー)となる。
エラーとならないように、Viteのフロントエンドの開発用サーバーの設定項目で以下のように設定しておくと、”/api”で始まるバックエンドへのリクエストがあったら”http://localhost:3000″に置き換えて中継してくれる。
export default defineConfig({
plugins: [react()],
server: { // フロントエンドの開発用Viteサーバーの設定
proxy: {
"/api": {
target: "http://localhost:3000",
changeOrigin: true
}
}
}
});
Proxyを設定しないと、フロントエンドとバックエンド間の通信がうまくいかないことがわかった。では開発環境ではなく、本番ではどうなるのか?本番ではフロントエンドとバックエンドが同じドメイン上で動作するのが一般的で、Proxy は不要になる。
理解が深まったところでバックエンドへ。いよいよPassportの出番か?と思ったら、ログインより先に、新規ユーザー登録(Sign Up)から作ったほうがいいと言われ、そりゃそうだ、となる。
脱線してばかりで申し訳ないが、Log in なのかLoginなのか迷ってAIに質問すると、以下の回答が得られたので、メモしておく。
The most commonly used combinations in modern web development are:
Login/Logout (as one word for components and URLs)
Sign up/Sign in (as two words in user-facing text)
Signup/Signin (as one word in component names and URLs)
というわけでSign Upのページも作り、ここまできたところで本日は終了。
くぅ… 今回は準備だけで終わってしまった。Passport.jsの続きはまた次回!
const app = express();
app.use(express.json());
app.post("/api/auth/signup", handleUserSignup);
app.post("/api/auth/login", handleUserLogin);
先週、宮崎の完熟きんかん「たまたま」を頂きました!
正確には、父が宮崎に行くというので、
この時期に行くなら「たまたま」を買ってきてほしい!と頼んで買ってきてもらいました😋
