인증은 백엔드 개발에서 가장 중요하지만 종종 오해를 받는 측면 중 하나입니다. 복잡성으로 인해 개발자는 Auth0 또는 Supabase와 같은 타사 솔루션을 자주 사용합니다. 이는 훌륭한 도구이지만 자체 인증 시스템을 구축하면 더 큰 유연성과 제어 기능을 제공할 수 있습니다.
이 가이드에서는 종속성을 최소화하면서 Express.js API 서비스를 위한 간단한 인증 미들웨어를 구현하는 방법을 알아봅니다. 최종적으로는 다음을 얻게 됩니다.
이 가이드는 단순성에 중점을 두고 있으며 복잡성을 줄이기 위해 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() );
다음으로 API 엔드포인트를 보호하기 위해 JWT 인증 미들웨어를 생성합니다. 이 예에서는 대칭 암호화를 사용합니다. 마이크로서비스 아키텍처의 경우 공개/개인 키 쌍을 사용한 비대칭 암호화 사용을 고려해보세요.
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!