はじめに
ユーザー認証を実装したい。
スマホアプリ向けのAPI作っているときにユーザー認証が必要となり、JWTが良さそうだったので採用した。
以下、注意点
- 本記事の目的はあくまでJWTの導入なので、脆弱性については触れていない。
- 入門者にわかりやすくするため、有識者には誤解される表現を使っている可能性がある。
JWTとは
「Json Web Token」の略で、URLに埋め込めるJSON文字列のこと。
電子署名によってJSONが改ざんされていないかをチェックできるため、ユーザー認証などに使うことができる。
筆者は詳しく理解してないので、JWT使えばログイン機能作れる!、みたいに思っている。
JWTのしくみ
- クライアントは、認証情報(ユーザーIDとパスワード)をサーバに送る(いわゆるログイン処理)
- サーバは、データベースに接続して認証情報が正しいか確認
サーバは、ユーザーIDと有効期限といった情報を持つJSONを秘密鍵で暗号化し、JWTの形でクライアントに返す- サーバは、ユーザーIDと有効期限といった情報を持つJSONに秘密鍵で署名し、JWTの形でクライアントに返す
- 以降、クライアントは、渡されたJWTを使ってサーバと通信する(いわゆるログイン状態)
JWTの有効期限が切れたら、再び1に戻って新しくJWTを発行する必要がある。
コード
command
npm install jsonwebtoken
index.js
var express = require("express");
var app = express();
var jwt = require("jsonwebtoken");
// 秘密鍵設定、本当は環境変数使うべき
app.set("superSecret", hogehoge);
var apiRoutes = express.Router();
// ユーザー情報、本当はデータベースにあるのをとってくるべき
var users = [
{id: "user0", pass: "huga0"},
{id: "user1", pass: "huga1"},
{id: "user2", pass: "huga2"}
];
// クライアントから送られたIDとパスワード確認してtoken(jwt)発行
// "/api/authenticate"
apiRoutes.post("/authenticate", (req, res) =>{
var post_id = req.body.postId;
var post_pass = req.body.postPass;
for (var i = 0; i < users.length; i++) {
// IDとパスワードが正しかったらtoken発行
if (post_id == users[i].id && post_pass == users[i].pass) {
var token = jwt.sign(user_id, app.get("superSecret"), {algorithm: HS256, expiresIn: 120});
res.json({
success: true,
msg: "Authentication successfully finished",
token: token
});
return;
}
}
// IDとパスワードが正しくなかった場合
res.json({
success: false,
msg: "Authentication failed"
});
});
// 一度クライアントに返したtokenが改ざんされずにクライアントから送られてきたか確認
apiRoutes.use((req, res, next) => {
var token = req.body.token;
// tokenがない場合、アクセスを拒否
if (!token) {
return res.status(403).send({
success: false,
msg: "No token provided"
});
}
// tokenが改ざんされていないかチェック
jwt.verify(token, app.get(superSecret), (err, decoded) => {
// tokenが不正なものだった場合、アクセス拒否
if (err) {
console.log(err);
return res.json({
success: false,
msg: "Invalid token"
});
}
// 正しいtokenの場合、認証OKする
req.decoded;
next();
});
});
// 認証後、これ以降のURIにアクセス可能となる
// "/api/private"
apiRoutes.get("/private", (req, res) => {
res.json({
msg: "Hello world!"
});
});
app.use("/api", apiRoutes);
解説
jwt.sing(user_id, app.get("superSecret"), {algorithm: HS256, expiresIn: 120});
- アルゴリズム
-
algorithm: HS256
デフォルト値がこれになっており、記述しなくても問題ない
-
- 有効期限
-
expiresIn: 120
120ミリ秒 -
expiresIn: 3h
3時間 -
expiresIn: 2days
2日
-
apiRoutes.post("/authenticate", (req, res) => {...});
apiRoutes.get("/private", (req, res) => {...});
アクセスする際のURIは、"/authenticate"
や"/private"
ではなく、
/api/authenticate
や"/api/private"
となる。
理由は、コードの一番下でapp.use("/api", apiRoutes);
としているから。
さいごに
脆弱性については今後別の記事でまとめる予定。
間違いなどあれば是非コメントにお願いします。