기존에 프론트엔드에서는 회원가입, 로그인 값들을 저장해둔 다음 POST요청을 보내기만 하면됐다..
로그인이라면 토큰을 받아서 로컬스토리지에 저장을 해주면 됐는데,,
개인프로젝트를 진행하면서 백쪽에서 회원가입, 로그인을 구현해보니 백쪽에서는 꽤 까다로운 절차가 있었다.
- 프론트에서 회원가입 정보를 받는다.
- 백쪽에서는 받은 정보를 바탕으로 DB에 저장한다. 비밀번호를 hash처리해서 저장.
- 프론트에서 로그인 정보를 받는다.
- 받은 email이 DB에 일치하는 정보가 있는지 확인한다.
- DB에 일치하는 정보가 있다면 받은 비밀번호와 hash처리된 비밀번호가 일치하는지 검증한다.
- 일치한다면 jwt를 발행.
비밀번호를 hash처리하기 위해 솔팅(salting)과 키스트레칭(keystretching)를 활용하여 단방향 암호와를 하는 라이브러리이다.
이번 프로젝트에서 비밀번호 저장을 위해 사용하였다.
npm i bcrypt
import bcrypt from "bcrypt"
//해쉬 처리
const hashedPassword = bcrypt.hash(평문 비밀번호, Cost Factor) //CostFactor란 key stretching의 수
//검증
bcrypt.compare(받은 비밀번호, hashedPassword)
JSON Web Token은 클라이언트와 서버간 정보를 JSON의 형태로 안전하게 전송하기 위한 개방형 표준이다.
자세한 내용은 다음에 더 알아보도록...
npm i jsonwebtoken
토큰화는 sign 메서드, 검증은 verify메서드를 활용한다.
import jwt from "jsonwebtoken"
const token = jwt.sign({email : "" , password : ""} , secretKey) // 토큰화
const verify = jwt.verify(token, secretKey) //토큰 검증
1) sign
sign메서드에서는 첫 번째 인자로 토큰으로 전달할 내용들이 들어간다. 두 번째 인수로 secret key가 들어가는데 노출되지 않도록 환경변수로 관리해 줘야한다.
세 번째 인수로 token의 옵션을 설정할 수 있고 옵션이 들어가지 않으면 HS256 알고리즘으로 JWT가 발급된다.
2) verify
verify메서드는 첫 번째 인자로 토큰이 들어가고 두 번째 인자로 토큰을 발급할 때 썼던 secretkey가 들어간다.
//GraphQL mutation 타입정의
type Auth {
token: String
User: User
}
type User {
id: Int!
user_name: String!
email: String!
password: String!
nickname: String!
}
type Mutation {
signup(
id: Int
user_name: String!
email: String!
password: String!
nickname: String!
): User
login(email: String!, password: String!): Auth
}
회원가입 resolvers
const resolvers = {
Mutation : {
signup: async(_parent, { email, password, nickname, user_name }, _ctx) => {
const hashPassword = await bcrypt.hash(password, 12);
//받은 정보의 비밀번호를 hash처리 한다.
await client.User.create({data: { email, nickname, user_name, password: hashPassword },
// 받은정보를 User DB에 저장하고 비밀번호만 Hash처리해서 저장.
});
}
}
}
로그인 resolvers
const resolvers = {
Mutation: {
login: async (_parent, { email, password }, _ctx) => {
const loginUser = await client.User.findUnique({
where: {
email,
},
});
//로그인시 받은 정보를 바탕으로 prisma에서 유저 email정볼르 찾는다.
if (!loginUser) {
throw new Error("등록되지 않은 이메일입니다.");
}
//만약 db에 저장되지 않은 email이라면 오류 반환
const passwordMatch = await bcrypt.compare(password, loginUser.password);
// 받은 유저 정보의 비밀번호와, db에 저장된 hash처리된 비밀번호를 검증한다.
if (!passwordMatch) {
throw new Error("비밀번호가 일치하지 않습니다.");
}
// 일치하지 않다면 오류반환
const token = jwt.sign(
{
email: loginUser.email,
password: loginUser.password,
},
process.env.SECRET_KEY
);
//일치한다면 jwt를 발급
return { token, loginUser };
},
},
};