首页 > web前端 > js教程 > 在 Node.js 中进行身份验证的正确方法 [uide]

在 Node.js 中进行身份验证的正确方法 [uide]

Barbara Streisand
发布: 2024-12-12 20:08:17
原创
639 人浏览过

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
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板