TypeScriptでpassport-jwtを用いたapiサーバー作成
概要
TypeScript と passport-jwt で認証が必要な Web API を作っている記事が見当たらなかったので勉強も兼ねて作成しました。
GitHub のソースコードは以下になります。
環境構築
node のインストール方法は以下の記事のNode.js 14.17.0 をインストールまで参照してください。
プロジェクトを作成
ディレクトリを作成。
mkdir api-passport-jwt-typescript
cd $_
git init
package をインストールします。
yarn init -y
yarn add cookie-parser express nodemon passport passport-jwt passport-local
typesync
yarn
package.json を以下のように編集してください。
{
"name": "api-passport-jwt-typescript",
"version": "1.0.0",
"main": "src/index.ts",
"license": "MIT",
"scripts": {
"start": "nodemon ./src/index.ts"
},
.
.
.
}
環境変数
./.env
を作成し、環境変数を記述します。
.env
の内容です。
JWT の秘密鍵を記述していますが、今回は簡略化のため以下のようになっていて、本来は乱数を使うべきです。
JWT_SECRET="secret-jwt-cat"
その他
.gitignore
を以下のように作成します。
.env と/node_modules を .git
の管理から外します。
/node_modules
.env
実装
最終的なディレクトリ構成は以下になります(node_modules と隠しファイルは除外して表示しています)。
.
├── package.json
├── src
│ ├── index.ts
│ ├── lib
│ │ └── security
│ │ └── index.ts
│ └── route
│ ├── cat.ts
│ ├── dog.ts
│ └── user.ts
└── yarn.lock
4 directories, 7 files
passport、 passport-local、 passport-jwt の設定
まず、./src/lib/security/index.ts
を作成し、passport
, passport-local
, passport-jwt
の設定を記述します。
それぞれのコメント文で内容を解説しています。
1 で passport-local の設定をします。username と password が一致するときにtrue
、それ以外はfalse
を返す処理です。
2 で passport-jwt の設定をします。jwt の検証をします。
3 において最後の設定したpassport
をexport
します。
import passport from "passport";
import { Strategy as LocalStrategy } from "passport-local";
import {
Strategy as JWTStrategy,
ExtractJwt,
StrategyOptions,
} from "passport-jwt";
// 1 passport-localの設定
passport.use(
new LocalStrategy(
{
usernameField: "username",
passwordField: "password",
session: false,
},
(username: string, password: string, done: any) => {
if (username === "hoge" && password === "fuga") {
return done(null, username);
} else {
return done(null, false, {
message: "usernameまたはpasswordが違います",
});
}
}
)
);
// 2 passport-jwtの設定
const opts: StrategyOptions = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET,
};
passport.use(
new JWTStrategy(opts, (jwt_payload: any, done: any) => {
done(null, jwt_payload);
})
);
// 3 passportをexport
export default passport;
Express の設定
先に Express の説明をします。
1 は Express の設定なので説明を省きます。
2 で、passport.initialize()
によって、passport
の初期化します。
3 において router を追加しています。/user
と/cat
にはpassport
の設定をせずに、/dog
のみpassport
の設定をしています。こうすることで、/dog
以下のすべてのパスにpassport
の認証を必須にできます。
import express from "express";
import cookieParser from "cookie-parser";
import passport from "./lib/security";
import catRouter from "./route/cat";
import userRouter from "./route/user";
import dogRouter from "./route/dog";
// 1 expressの設定
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
// 2 passportを初期化
app.use(passport.initialize());
// 3 routerを追加
app.use("/user", userRouter);
app.use("/cat", catRouter);
app.use("/dog", passport.authenticate("jwt", { session: false }), dogRouter);
app.listen(3000, () => {
console.log("listen to " + 3000);
});
router の設定
./src/route
に routing の設定します。
作成したファイルは./src/route/cat.ts
、./src/route/dog.ts
、./src/route/user.ts
です。
./src/route/user.ts
/login
に post 送ると、jwt が返ってくるスクリプトです。
1 の箇所で、jwt の token 作成と制限時間の設定をしています。
制限時間は 1m(60 秒)に設定しています。
import express from "express";
import jwt from "jsonwebtoken";
import passport from "../lib/security";
const router = express.Router();
router.post(
"/login",
passport.authenticate("local", { session: false }),
(req, res, next) => {
// 1 jwtのtokenを作成
const user = req.user;
const payload = { user: req.user };
const token = jwt.sign(payload, process.env.JWT_SECRET as string, {
expiresIn: "1m",
});
res.json({ user, token });
}
);
export default router;
./src/route/cat.ts
1 では psssport を使用しないため、jwt の認証が不要です。
逆に、2 において passport を使用することで、jwt の認証がなければ結果が返ってきません。
import { Router } from "express";
import passport from "../lib/security";
const router = Router();
// 1 jwtが不要なapi
router.get("/public", (req, res, next) => {
res.json("public cat");
});
// 2 jwtが必要なapi
router.get(
"/private",
passport.authenticate("jwt", { session: false }),
(req, res, next) => {
res.json("private cat");
}
);
export default router;
./src/route/dog.ts
Express の設定で、passport
の認証必須にしたので、ここで記述しなくても認証が付与されています。
import { Router } from "express";
const router = Router();
router.get("/1", (req, res, next) => {
res.json("number of dog is 1");
});
router.get("/2", (req, res, next) => {
res.json("number of dog is 2");
});
export default router;
動作確認
実行
環境変数の読み込みを以下で実装します。
dotenv というライブラリがありますが、本番環境に.env ファイルを置くことはしたくないので、今回はコマンドで読み込ませます。
export $(cat .env | grep -v ^# | xargs)
yarn start
で起動します。
yarn start
api の確認
Postman で実行確認をします。
token を生成していない場合。
localhost:3000/cat/public
はpublic cat
が返ってきます。
それ以外のlocalhost:3000/cat/private
、localhost:3000/dog/1
とlocalhost:3000/dog/2
はUnauthorized
が返ってきます。
localhost:3000/cat/private
に get した例
localhost:3000/dog/1
に get した例
localhost:3000/dog/2
に get した例
token を生成する。
localhost:3000/user/login
に post することで、token が返ってきます。
localhost:3000/login
に post した例
Bearer Token の Token に上記で取得した token を指定することによって、認証が可能です。
localhost:3000/cat/private
に token つきで get した例
localhost:3000/dog/1
に token つきで get した例
localhost:3000/dog/2
に token つきで get した例
また、1 分すぎるとUnauthorized
に戻ります。
1 分すぎた例
参考
参考にしたソースコード
参考にした記事
Discussion