首頁 > web前端 > js教程 > 在 Node.js 中進行身份驗證的正確方法 [uide]

在 Node.js 中進行身份驗證的正確方法 [uide]

Barbara Streisand
發布: 2024-12-12 20:08:17
原創
652 人瀏覽過

The Right Way to do Authentication in Node.js [uide]

身份驗證是後端開發中最關鍵但經常被誤解的方面之一。由於其複雜性,開發人員經常轉向第三方解決方案,例如 Auth0 或 Supabase。雖然這些都是優秀的工具,但建立自己的身份驗證系統可以提供更大的靈活性和控制力。

在本指南中,您將了解如何以最少的依賴關係為 Express.js API 服務實作簡單的驗證中間件。到最後,您將擁有:

  • 功能齊全的使用者名稱密碼驗證。
  • 與 PostgreSQL 整合來儲存使用者帳戶。
  • 基於 JWT 的身份驗證中間件。
  • 透過自動重複使用偵測刷新令牌以增強安全性。

本指南著重簡單性,避免使用諸如 Passport.js 之類的包來降低複雜性。


設定使用者帳戶表

首先,建立一個 PostgreSQL 表來儲存使用者帳戶:

CREATE TABLE users (
    "id" SERIAL PRIMARY KEY,
    "username" VARCHAR(255) UNIQUE NOT NULL,
    "password" VARCHAR(255) NOT NULL,
    "email" VARCHAR(255) UNIQUE,
    "created_at" TIMESTAMP NOT NULL DEFAULT NOW()
);
登入後複製

JWT 驗證中介軟體

接下來,建立 JWT 驗證中間件來保護 API 端點。此範例使用對稱加密。對於微服務架構,請考慮使用公鑰/私鑰對的非對稱加密。

中間件代碼(/src/middleware/jwt.ts):

import jwt from "jsonwebtoken";

const JWT_SECRET_KEY = process.env.JWT_SECRET_KEY as string; // Randomly generated. Min length: 64 characters

export const protectedRoute: RequestHandler = async (req, _, next) => {
  const authHeader = req.header("authorization");

  if (!authHeader) {
    return next(notAuthenticated());
  }

  const accessToken = authHeader.replace(new RegExp("\b[Bb]earer\s"), "");

  try {
    const { userId } = validateJWT(accessToken);
    const user = await userRepository.getUserById(parseInt(userId));

    if (user) {
      req.user = user;
      next();
    } else {
      next(invalidAccessToken());
    }
  } catch (err) {
    next(invalidAccessToken());
  }
};

const validateJWT = (token: string, verifyOptions?: jwt.VerifyOptions) => {
  const jwtVerifyOptions = Object.assign(
    { algorithms: "HS256" },
    verifyOptions,
    {
      issuer: "yourAPI.com",
      audience: "yourAPI.com:client",
    }
  );
  return jwt.verify(token, JWT_SECRET_KEY, jwtVerifyOptions) as T;
};
登入後複製

使用中間件來保護路由:

import { protectedRoute } from "@/middleware/jwt";

router.get("/user", protectedRoute, async (req, res, next) => {
  const user = req.user!;
  res.json({ user });
});
登入後複製

建立身份驗證控制器

現在,實作用於註冊和登入的控制器:

註冊控制器:

import argon from "argon2";

const signup = async (props) => {
  const { username, password, email } = props;

  await userRepo.getUser(username).then((res) => {
    if (res !== null) throw usernameNotAvailable();
  });

  const hashedPass = await argon.hash(password, {
    timeCost: 2,
    parallelism: 1,
    memoryCost: 19456,
  });

  const newUser = await createUser({
    username,
    hashedPass,
    email,
  });

  const refreshToken = await generateRefreshToken(newUser.userId);
  const accessToken = generateAccessToken(newUser.userId);

  const { password: _, ...userRes } = newUser;
  return { user: userRes, accessToken, refreshToken };
};
登入後複製

登入控制器:

const login = async (props) => {
  const { username, password } = props;

  const user = await getUser(username).then((res) => {
    if (res === null) throw invalidLoginCredentials();
    return res;
  });

  const isOk = await argon.verify(user.password, password);

  if (isOk) {
    const refreshToken = await generateRefreshToken(user.userId);
    const accessToken = generateAccessToken(user.userId);

    const { password: _, ...userRes } = user;
    return { user: userRes, accessToken, refreshToken };
  }

  throw invalidLoginCredentials();
};
登入後複製

儲存刷新令牌

刷新令牌提供長期身份驗證。讓我們建立一個資料庫表來儲存它們:

CREATE TABLE refresh_tokens (
    "id" SERIAL PRIMARY KEY,
    "token" UUID NOT NULL DEFAULT gen_random_uuid(),
    "token_family" UUID NOT NULL DEFAULT gen_random_uuid(),
    "user_id" INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    "active" BOOLEAN DEFAULT true,
    "expires_at" TIMESTAMP NOT NULL,
    "created_at" TIMESTAMP NOT NULL DEFAULT NOW()
);
登入後複製

代幣生成器:

import jwt from "jsonwebtoken";

const JWT_SECRET_KEY = process.env.JWT_SECRET_KEY as string; // Randomly generated. Min length: 64 characters

const generateAccessToken = (userId: number) => {
  const jwtSignOptions = Object.assign(
    { algorithm: "HS256" },
    {},
    {
      issuer: "yourAPI.com",
      audience: "yourAPI.com:client",
    }
  );
  return jwt.sign({ userId: userId.toString() }, JWT_SECRET_KEY, jwtSignOptions);
};

const generateRefreshToken = async (userId: number, tokenFamily?: string) => {
  const expAt = new Date(new Date().getTime() + 31 * 24 * 60 * 60 * 1000); // Expire in 31 days
  const refreshTokenExp = expAt.toISOString();

  const token = await createTokenQuery({
    userId,
    tokenFamily,
    expiresAt: refreshTokenExp,
  });

  return token;
};
登入後複製

刷新令牌邏輯:

實作邏輯來安全地處理刷新令牌:

const refreshToken = async ({ token }: RefreshTokenSchema) => {
  const tokenData = await getRefreshToken(token);

  if (!tokenData) throw forbiddenError();

  const { userId, tokenFamily, active } = tokenData;

  if (active) {
    // Token is valid and hasn't been used yet
    const newRefreshToken = await generateRefreshToken(userId, tokenFamily);
    const accessToken = generateAccessToken(userId);

    return { accessToken, refreshToken: newRefreshToken };
  } else {
    // Previously refreshed token used, invalidate all tokens in family
    await invalidateRefreshTokenFamily(tokenFamily);

    throw forbiddenError();
  }
};
登入後複製

在這篇 Auth0 文章中了解有關刷新令牌和自動重複使用偵測的更多資訊。


結論

透過遵循本指南,您已經為 Node.js API 建立了一個簡單、安全的身份驗證系統,並且依賴性最小。這種方法可確保您擁有完全控制權並遵守現代最佳安全實務。

如果您想節省時間和精力,請查看 Vratix。我們的開源 CLI 可以在幾秒鐘內建立一個功能齊全的 Node.js 專案並進行身份驗證。在 GitHub 上探索我們完全實現的身份驗證模組。


本指南對您有幫助嗎?請在評論中告訴我們,或透過 X 與我們聯繫!

以上是在 Node.js 中進行身份驗證的正確方法 [uide]的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板