TIL: passport, withCredential, MySQL

엉썬·2022년 4월 6일
0

Needog

목록 보기
2/8

Passport

Passportexpress 서버 위에서 제대로 동작하지 않는 문제가 발생했다.CORS도 잘 지키고 나름대로 로직도 제대로 짰다고 생각했는데도 문제가 발생했다.
브라우저의 devtool을 살펴보니 application 섹션에 쿠키가 전송되지 않는 문제가 있었다. 왜 그런지 몰라서 다짜고짜 passport와 express를 활용해서 서버를 구축한 사례를 접해보니 몇가지 수정해야 할 이슈을 알 수 있었다.

이슈 목록
1. Passport에서 form 형식을 사용해서 데이터를 주고 받을 시에 data의 key값을 passport에서 요구하는 값과 동일하게 설정해야 한다.
2. withCredential 옵션을 통해서 중요한 데이터를 주고 받아야 한다.

이슈 1. passport와 <form/>

아래는 처음에 Front에서 작성한 코드의 일부이다.

//초기 코드
return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="userId">아이디</label>
      <input type="text" id="userId" onChange={handleChange} />
      <label htmlFor="nickname">닉네임</label>
      <input type="text" id="nickname" onChange={handleChange} />
      <label htmlFor="pwd">비밀번호</label>
      <input type="password" id="pwd" onChange={handleChange} />
      <button type="submit">가입하기</button>
    </form>
  );

Form을 통해서 express에는 req.body로 전송이 되는데 이때 req.body = {userId: 000, nickname: 000, pwd:000}로 전송이 된다.
Passport의 documentation에는 form 형식으로 데이터를 전송하는 경우에 props의 이름을 맞추어 달라는 부분이 나온다. (참고 :Documentation: Username & Password)

따라서 해결책으로는 (1)Client 측에서 네이밍 컨벤션을 준수하여 전송하는 방법과 (2)서버측에서 받은 데이터의 usernameField와 passwordField에 명시적으로 알려주는 방법을 통해서 해결할 수 있다.

const strategy = new LocalStrategy(
  { usernameField: "id", passwordField: "pwd" }, //이 부분이 추가
  async (id, password, done) => {
	//...verification 로직
  }
);

해결책은 LocalStrategy 생성자 함수의 verify function 앞에 usernameField와 passwordField의 이름을 object 형태의 인자로 전달하면 된다.

이슈 2. withCredential Option

그런데 웬 걸. 그렇게 하고도 cookie가 전송이 되지 않아서 session이 제대로 작동하지 않았다. session이 제대로 작동하지 않으니 passport도 미들웨어로서의 역할을 수행하지 못하여 req.user을 찾아볼 수 없었다. axios의 문제인가도 싶었다. CORS도 분명 제대로 설정해 두었는데 왜일까 하여 다시 웹 서칭을 했다.

찾아 보니 credential 옵션을 서버와 클라이언트 측 모두 설정해야 한다고 한다. 클라이언트 측과 서버 측에서 credential을 설정하는 헤더는 조금 이름이 다르다. 클라이언트 측에서는 withCredentials:true로 설정해야 하고, 서버 측에서는 Access-Control-Allow-Credentials: true로 설정해야 한다. 궁금하여 서버와 클라이언트 각각 한 쪽만 설정을 해보니 제대로 작동하지 않았다.

나는 Client에서는 axios를 사용하고 Server에서는 Cors 패키지를 사용하고 있으므로 설정은 간단했다. axios에서는 config를 통해 설정하면 되고, cors에서는 credentials:true로 설정하면 된다.

또 하나 중요한 점은 credential을 설정한 경우, Access-Control-Allow-Origin 헤더 필드에 명시적으로 값을 알려주어야 한다. "*"는 사용할 수 없다.

axios.post("url", data, { withCredentials: true });
//혹은 전역적으로 axios.defaults.withCredentials = true

cors({ credentials: true, origin: true })

트리비아

  1. 검색을 하던 중 XMLHttpRequest에서만 발생한다는 뉘앙스의 말이 있길래 Fetch도 사용해 보았지만 같은 문제가 발생했다.

  2. 개인적으로 zerocho님의 설명이 도움이 되었다.

참고

MySQL

또 MySQL 로직에서 문제가 있었다. 이젠 지겹지만 아직도 계속 오류가 있겠지...? 🥲

cookie가 제대로 브라우저 측에 전달되고 또 서버 측에서 받아 sessionpassport 문제는 해결되었다. 결과적으로 로그인이 제대로 작동하는 듯 보였다. 하지만 이상하게도 로그인까지는 성공이 되었는데 req.isAuthenticate 가 제대로 작동하지 않아서 user 객체가 로그인을 제외하고는 반환되지 않는 현상이 있었다.

// database/sql.js

findUser: async (where) => {
    const [key, val] = Object.entries(where)[0];

    try {
      // Before
      // const user = await ...
      // return user;
      const [result] = await promisePool.query(
        `SELECT * FROM users WHERE ${key}="${val}"`
      );
      const [user] = result;
      return user;
    } catch (error) {
      console.log(error);
    }
  },

보아하니 쿼리의 반환값이 단 한 명의 유저를 담은 객체를 반환한다고 나 혼자서 생각했다. 그러나 생각해보면 쿼리는 limit과 같은 제한이 있던 것도 아니었기 때문에 당연히 Array를 반환할 터였다.
따라서 Strategy에 전달된 user의 값은 user 오브젝트가 아니라 user[]이었다.
따라서 sql로직을 수정하여 유저 객체를 전달하도록 하였다.

// passport/localStrategy.js

const strategy = new LocalStrategy(
  { usernameField: "id", passwordField: "pwd" },
  async (id, password, done) => {
    try {
      const user = await findUser({ userId: id });
      //if (user.length) {
      if (user) {
        //생략
      }
    } catch (error) {
      console.error(error);
      done(error);
    }
  }
);

어쩐지 반환한 유저에 대해서 if(user)로 유저의 진위여부가 판단되지 않길래 대충 임기응변으로 user.length값을 if문의 조건으로 넣었는데 sql query에서 진작에 꼬인 까닭이었다.

[
	{
    ...
    }
]

terminal에서 이런 식으로 나오니까 자꾸 데이터가 길어지면 bracket을 그냥 print 문에 포함된 것으로 읽게 되버리는 것 같다. 방심하지 말자ㅜㅜ

profile
하던 일부터 끝내자

0개의 댓글