身份验证是后端开发中最关键但经常被误解的方面之一。由于其复杂性,开发人员经常转向第三方解决方案,例如 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 与我们联系!
以上就是在 Nodejs 中进行身份验证的正确方法 [uide]的详细内容,更多请关注其它相关文章!