4주 프로젝트 - dev log #9

Joshua Song / 송성현·2020년 2월 11일
0

이머시브_16

목록 보기
27/29
post-custom-banner

오늘은 JWT를 이용한 모바일 인증을 구현하기로 해서 관련된 레퍼런스를 찾아보고 상세히 설명해주는 영상을 보면서 구현했다. 그리고 인증하는 부분을 위한 서버를 따로 분리하기로 해 그것도 실행했다.

먼저 모바일 인증을 하는 방법을 여러가지 인데 우리 팀은 우리가 이미 한번 구현해보고 더 익숙한 JWT를 사용해 인증을 구현하기로 했다.

JWT의 핵심은 토큰을 발급해 그 토큰을 가지고 있는지 없는지, 있다면 토큰의 정보가 일치하는지 안 하는지로 인증을 해준다. 일단 인증 부분이 필요한 로그인 부분에서 이 파트를 구현하기로 했다.

토큰을 구현하는 부분에 있어 액세스와 리프레쉬 토큰을 사용해주기로 했는데 액세스 토큰은 말그대로 접속을 할 때 접근 권한을 주는 토큰이고 리프레쉬 토큰은 이 액세스 토큰이 만료되었을 때 요청이 오면 새로 액세스 토큰을 보내주는 역할을 한다. 그리고 이 리프레쉬 토큰이 만료되면 이제 로그아웃이 되어 다시 로그인이 필요한 것이었다. 각 토큰에는 maxAge라는, 만료 기한을 설정해줄 수 있다.

res.cookie("accessToken", accessToken, { maxAge: 1000 * 60 * 60 * 24, signed: true });

api.use(cookieParser(process.env.TOKEN_KEY))

이런식으로 설정을 해주는 것인데 cookie-parser라는 모듈을 사용해서 쿠키에 담아줄 수 도 있다. signed 부분은 토큰을 쿠키에 담을 때 외부에서 볼 수 없게 암호화 해주는 것인데 cookieParser를 적용할 때 괄호 안에 넣어주는 키를 기반으로 서명 (암호화)을 해주는 것이다. 저걸 읽으려면 res.cookie 가 아닌 res.signedCookies로 봐야 한다.

https://www.youtube.com/watch?v=mbsmsi7l3r4

이 강좌가 많은 도움이 되었다!

흐름을 따라서 알맞게 토큰을 만들어 주었다. 중요한 정보를 담아주는 곳으로 쿠키에 담아두기로 한 이유는 만약에 토큰이 아닌 정말로 중요한 금융정보 같은, 매우 귀중한 정보였다면 키스토어나 키체인 같이 체계적인 보안이 된 저장소에 저장을 했을 텐데 우리의 경우 로그인 상태 유지를 위한 정보 저장이었기 때문에 로컬 쿠키에 저장해도 괜찮을 것이라 판단했다.

const router:express.Router = express.Router();
const refresKey:any = process.env.JWT_SECRET_REFRESH;

const generateAccessToken = (payload:{id:number, nickname:string, email:string}) => {
    const accessKey:any = process.env.JWT_SECRET_ACCESS;
    const options:{expiresIn:string} = { expiresIn: "1d" };
    return jwt.sign(payload, accessKey, options);
};

router.post("/signin", async (req:express.Request, res:express.Response) => {
    const { email, password }:{email:string, password:string} = req.body;
    if (!email) {
        res.status(409).send("Email is required");
        return;
    }
    if (!password) {
        res.status(409).send("Password is required");
        return;
    }
    try {
        const user:User|undefined = await getConnection()
            .createQueryBuilder()
            .select("user")
            .from(User, "user")
            .where("user.email = :email", { email })
            .getOne();
        // ! 유효하지 않은 이메일
        if (!user) {
            res.status(401).send("Invalid Email");
            return;
        }

        // ? 암호화 후 비교
        const pdkdf2Promise:Function = util.promisify(crypto.pbkdf2);
        const key:Buffer = await pdkdf2Promise(password, user.salt, 105123, 64, "sha512");
        const encryPassword:string = key.toString("base64");
        if (encryPassword !== user.password) {
            res.status(401).send("Incorrect Password.");
            return;
        }
        // ! 토큰 발급
        const payload:{id:number, nickname:string, email:string} = {
            id: user.id,
            nickname: user.nickname,
            email: user.email,
        };

        // ? accessToken
        const accessToken = generateAccessToken(payload);

        // ? refresh Token
        // const refresKey:any = process.env.JWT_SECRET_REFRESH;
        const refreshToken = jwt.sign({ id: user.id }, refresKey, { expiresIn: "30d" });

        const result:UpdateResult = await getConnection().createQueryBuilder()
            .update(User).set({ refreshToken })
            .where({ id: user.id })
            .execute();

        if (result.raw.affectedRows === 0) {
            res.status(409).send("Failed to insert Token");
            return;
        }

        // * token을 어디에 저장할것인가?
        // * -> 일단 cookie
        res.clearCookie("refreshToken");
        res.cookie("accessToken", accessToken, { maxAge: 1000 * 60 * 60 * 24, signed: true });
        res.cookie("refreshToken", refreshToken, { maxAge: 1000 * 60 * 60 * 24 * 30, signed: true });

        res.status(201).send("User signed in");
    } catch (e) {
        console.log(e);
        res.status(400).send(e);
    }
});

서버 분리는 포트를 새로 파서 인증을 포함한 부분만 따로 옮겨놓았다. 조금 번거로운 작업이었지만 그래도 수월하게 분리할 수 있었다. 프런트 분들은 꾸준히 열심히 코드를 짜고 계셨고 이제 에러들을 잡고 마무리 하면 프런트로 넘어갈 수 있을 것 같다.

profile
Grow Joshua, Grow!
post-custom-banner

0개의 댓글