+4

🔐Node.js ExpressでのJSON Web Tokens(JWT)による安全な認証と承認

JSON Web Tokens(JWT)とは

JSON Web Tokens(JWT)は、情報をJSONオブジェクトとして安全に伝達するためのコンパクトで自己完結型の方法を定義するオープン標準(RFC 7519)です。 JWTは、クライアントのアイデンティティを検証し、クライアントの主張や権限に基づいて保護されたリソースへのアクセスを許可するため、認証と承認に特に役立ちます。

この記事では、Node.js ExpressアプリケーションでのJWTによる安全な認証と承認の使用について説明します。次のトピックについて説明します。

  1. JWTの仕組み
  2. Node.js Expressアプリケーションのセットアップ
  3. JWTを利用した認証の実装
  4. JWTを利用した承認の実装
  5. ベストプラクティスとセキュリティの考慮事項

JWTの仕組み

JWTの構造

JWTは、ヘッダー、ペイロード、署名の3つの部分で構成されています。これら3つの部分は、base64Urlエンコードされ、ピリオド(.)区切りで連結され、JWT全体を文字列として形成します。 JWTの構造は次のようになります。

header.payload.signature

ヘッダー

ヘッダーには通常、以下の2つのプロパティが含まれます。

  • alg:使用されている署名アルゴリズム(例:HMAC SHA256(HS256)またはRSA(RS256))。
  • typ:トークンのタイプ。通常、"JWT"に設定されます。

JSON形式のサンプルヘッダー:

{
  "alg": "HS256",
  "typ": "JWT"
}

ペイロード

ペイロードには、主体(例:ユーザー)に関する声明と追加のメタデータが含まれます。主張には3つのタイプがあります。

  • 登録済みの主張iss(発行者)、exp(有効期限)、sub(主題)、およびaud(聴衆)などの事前定義された主張。
  • パブリッククレーム:両当事者間で合意されたカスタムクレーム。衝突を避けるために、IANA JSON Webトークンレジストリに登録するか、衝突に対して耐性がある命名規則を使用する必要があります。
  • プライベートクレーム:2つの当事者間で使用され、一般公開を意図していないカスタムクレーム。

JSON形式のサンプルペイロード:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

署名

署名は、トークンの完全性を検証するために使用されます。署名は、エンコードされたヘッダー、エンコードされたペイロード、シークレット、およびヘッダーで指定されたアルゴリズムを組み合わせて生成されます。たとえば、HMAC SHA256アルゴリズムを使用する場合:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

トークンの検証プロセス

クライアントがJWTをサーバーに送信すると、サーバーはJWTをデコードし、トークンの作成時に使用された同じシークレットまたは秘密鍵を使用して署名を再計算することで、トークンの署名を検証します。再計算された署名がJWTの署名と一致する場合、サーバーはトークンの内容を信頼できます。

Node.js Expressアプリケーションのセットアップ

まず、Node.js Expressアプリケーションをセットアップする必要があります。 Node.jsとnpmがインストールされていることを確認してから、プロジェクトの新しいディレクトリを作成し、npm initで初期化します。プロンプトに答えた後、必要な依存関係をインストールします。

npm install express jsonwebtoken bcryptjs body-parser dotenv

JWTシークレットやパスワードソルトラウンドのような機密情報を格納するための.envファイルを作成します。

JWT_SECRET=my_jwt_secret
SALT_ROUNDS=10

JWTを利用した認証の実装

ユーザー登録

この例では、ユーザーデータの簡単なインメモリストレージを使用します。実際の環境では、データベースを使用してデータを永続的に保存することが一般的です。まず、次の内容を持つusers.jsファイルを作成します。

const users = [];

module.exports = users;

次に、authController.jsファイルを作成して、ユーザー登録と認証を処理します。必要な依存関係とusers配列をインポートします。

const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const dotenv = require('dotenv');
const users = require('./users');

dotenv.config();

bcryptjsを使用してユーザーのパスワードをハッシュし、users配列にユーザー情報を格納するための関数を作成します。

const register = async (req, res) => {
  const { username, password } = req.body;

  // Check if the user already exists
  const userExists = users.find((user) => user.username === username);
  if (userExists) {
    return res.status(400).send('User already exists');
  }

  // Hash the password
  const salt = await bcrypt.genSalt(parseInt(process.env.SALT_ROUNDS));
  const hashedPassword = await bcrypt.hash(password, salt);

  // Store the user
  const newUser = { username, password: hashedPassword };
  users.push(newUser);

  res.status(201).send('User registered successfully');
};

ユーザー認証

送信されたパスワードと格納されたハッシュを比較してユーザーを認証する関数を作成します。パスワードが正しい場合、JWTを生成し、クライアントに返します。

const authenticate = async (req, res) => {
  const { username, password } = req.body;

  // Find the user
  const user = users.find((user) => user.username === username);
  if (!user) {
    return res.status(404).send('User not found');
  }

  // Verify the password
  const isPasswordValid = await bcrypt.compare(password, user.password);
  if (!isPasswordValid) {
    return res.status(401).send('Invalid credentials');
  }

  // Create a JWT
  const token = jwt.sign({ username: user.username }, process.env.JWT_SECRET);

  res.status(200).json({ token });
};

最後に、registerauthenticate関数をエクスポートします。

module.exports = {
  register,
  authenticate,
};

JWTベースの承認の実装

次に、authMiddleware.jsという新しいファイルに、JWTを確認するミドルウェア関数を作成します。

const jwt = require('jsonwebtoken');
const dotenv = require('dotenv');

dotenv.config();

const verifyToken = (req, res, next) => {
  const authHeader = req.headers.authorization;

  if (!authHeader) {
    return res.status(401).send('Access denied: No token provided');
  }

  const token = authHeader.split(' ')[1];

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(400).send('Invalid token');
  }
};

module.exports = {
  verifyToken,
};

verifyTokenミドルウェアは、認証ヘッダーからJWTを取得し、jsonwebtokenライブラリを使用して検証します。 JWTが有効であることが確認された場合、デコードされたペイロードがreq.userに設定され、次のミドルウェアまたはルートハンドラーが呼び出されます。

次に、routes.jsファイルを作成し、保護されたルートを定義します。

const express = require('express');
const authMiddleware = require('./authMiddleware');

const router = express.Router();

router.get('/protected', authMiddleware.verifyToken, (req, res) => {
  res.status(200).send('Access granted to protected resource');
});

module.exports = router;

最後に、app.jsファイルを作成して、アプリケーションを配線します。 必要な依存関係、認証コントローラー、およびルートをインポートします。

const express = require('express');
const bodyParser = require('body-parser');
const authController = require('./authController');
const routes = require('./routes');

const app = express();
const port = process.env.PORT || 3000;

Expressミドルウェアを設定し、認証ルートを登録し、保護されたルートを使用します。

app.use(bodyParser.json());

// Authentication routes
app.post('/register', authController.register);
app.post('/authenticate', authController.authenticate);

// Protected routes
app.use('/', routes);

Expressサーバーを起動します。

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

これで、node app.jsでアプリケーションを実行し、PostmanのようなAPIクライアントを使用して/register/authenticate、および/protectedエンドポイントをテストできます。

ベストプラクティスとセキュリティの考慮事項

  1. JWTシークレットを安全に保存:環境変数、シークレットマネージャー、または構成管理ツールを使用して、JWTシークレットを安全に保存します。
  2. HTTPSを使用:JWTが通信中に傍受されるのを防ぐために、クライアントとサーバー間の通信には常にHTTPSを使用してください。
  3. 適切な有効期限を設定:JWTの有効期限を短くして不正使用のリスクを減らします。 JWTを作成するときに、expクレームに適切な値を設定できます。
  4. トークンの失効を処理:トークンブラックリストを使用して、失効したトークンのリストを保持することで、パスワードの変更やユーザーの権限の変更など、事前に設定された有効期限前にトークンを無効にすることができます。
  5. 不要な情報をJWTに含めない:JWTは通常、クライアントとサーバー間で公開されます。パスワード、セキュリティ質問の答え、または他の機密情報をJWTに含めないでください。
  6. JWTを適切に保護:JWTをクッキー、ローカルストレージ、またはセッションストレージに保存する場合は、それらを適切に保護してください。たとえば、HttpOnlyおよびSecure属性を持つクッキーを使用するか、クライアント側のJavaScriptからトークンを保護するためのライブラリや手法を検討してください。
  7. アルゴリズムの強力さを確保:JWTの署名に使用されるアルゴリズムは、強力で安全であることが重要です。また、JWTに対して「なし」アルゴリズムを使用できないように、バックエンドで受け入れられるアルゴリズムを厳密に制限してください。
  8. 適切なエラーハンドリングを実装:JWTの検証が失敗した場合や、トークンが期限切れの場合など、エラーを適切に処理して、クライアントに適切なエラーレスポンスを提供してください。

これで、Node.js ExpressアプリケーションでJWTによる安全な認証と承認を実装する方法が理解できたでしょう。JWTは、分散型システムやマイクロサービスアーキテクチャでの認証と承認に特に便利ですが、適切なセキュリティ対策とベストプラクティスに従うことが重要です。

Mình hy vọng bạn thích bài viết này và học thêm được điều gì đó mới.

Donate mình một ly cafe hoặc 1 cây bút bi để mình có thêm động lực cho ra nhiều bài viết hay và chất lượng hơn trong tương lai nhé. À mà nếu bạn có bất kỳ câu hỏi nào thì đừng ngại comment hoặc liên hệ mình qua: Zalo - 0374226770 hoặc Facebook. Mình xin cảm ơn.

Momo: NGUYỄN ANH TUẤN - 0374226770

TPBank: NGUYỄN ANH TUẤN - 0374226770 (hoặc 01681423001)

image.png


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí