passport

broccoli·2021년 4월 18일
1

Authentication

목록 보기
2/3
post-thumbnail

passportNode.js를 위한 인증 미들웨어 이다. 전략이라는 명칭으로 다양한 Federated Login을 도와준다.

passport을 사용하기 위해 알아둘 개념

passport는 사용하기엔 편할 수 있지만 사용하기 이전에 몇가지 개념에 대해서 미리 숙지 하고 있지 않으면 사용하면서도 이해하기 어렵다고 생각한다. 개인적으로 본인은 이해가 어려웠음. 따라서 아래 관련 개념을 간략히 정리 해보자면:

쿠키는 간단히 이야기하면 브라우저가 가지고 있는 작은 저장소라고 생각하면 된다.
쿠키는 서버쪽에서 응답에 Set-Cookie라는 키값으로 헤더에 실어서 응답에 같이 보내는 정보이다.

서버로 온 요청에 쿠키를 등록해 응답에 되돌려보낸 후 이후 요청부터는 브라우저로부터 매 요청시마다 쿠키를 같이 보내준다. 이 쿠키로 인해 서버는 이 요청이 어떤 곳에서부터 온 요청인지 파악할 수 있게 된다. (이말은 HTTP의 stateless, connectionless의 성질을 알면 이해가 된다.)

❗따라서 쿠키는 사용자의 치명적인 정보가 절대로 담는 용도로 사용되어선 안된다.
❗쿠키자체가 설사 아무 정보를 담고 있지 않다 할지라도 쿠키자체만으로도 사용자의 동의없이 탈취되어선 안된다.
❗따라서 운영환경에서는 쿠키의 여러 옵션(httpOnly, secure 등)을 통해 보안에 유의해야한다.

ℹ️ 만약 프론트백엔드서버가 서로 다를 경우 쿠키를 전달하기 위해서는 각 프론트 백엔드 서버 모두 credentials 값이 true 되어야한다.

Session

세션은 서버측에서 저장하고 있는 데이터이다. 보통 아주 아주 아주 기본적인 사용자의 대한 정보를 담는다. 이메일이나 사용자 id가 아닌 사용자를 식별할 수 있는 유니크한 값.

쿠키의 세션ID ➡️ 세션 값 ➡️ DB의 사용자 정보

위와 같은 방식으로 서버는 매 요청으로부터 사용자를 식별할 수 있다.

OAuth

OAuth는 페이스북, 트위터, 네이버, 카카오톡 등과 같은 (provider)가 제공하는 API를 사용할 수 있는 토큰발행을 받는 목적으로 한다.

이런 토큰들은 provider가 제공하는 resource를 사용할 수 있는 접근 권한도 포함되어 있다. 따라서 이런 토큰을 발행받기 위해서는 실제 소유자인 resource owner 에게 승인을 받는것이 필수이다. 승인을 받게 되면 providerclient(내 서버)를 해당 권한을 사용할 수 있게 토큰을 발행해준다.

토큰을 발행받은 client는 위임된 권한 내에서 provider가 제공하는 resource에 접근이 가능하다.

ℹ️ OAuth2.0은 OAuth1.0과 달리 토큰의 life-time을 지정할 수 있도록 함.

passport를 이해하는데 위에 개념이 어느정도 있다면 매우 쉽게 사용할 수 있을 것이라고 생각함.

passport 사용법

passport는 크게 초기화, 전략구현, 라우트 파트로 나누어 구현하면 된다.

초기화

✅ passport는 세션을 사용하기 때문에 세션의 미들웨어 아래에 초기화를 해줘야한다.

app.use(
  session(
    process.env.NODE_ENV === 'production'
      ? sesseionProdOption
      : sesseionDevOption
  )
)
app.use(passport.initialize())
app.use(passport.session())

전략구현

Top-level 폴더에 passport를 만들고 그 아래 전략들을 구현하여 모듈화하여 구현하면 관리하기 편하다.

serializeUser, deserializeUserpassport폴더내 index.js에서 구현하고 이것을 app에 호출해준다.

// passport/index.js
module.exports = () => {
  passport.serializeUser((user, done) => {
    return done(null, user.id)
  })
  passport.deserializeUser(async (id, done) => {
    try {
      console.log('🪀🪀🪀 deserializeUser')
      const user = await User.findOne({
        where: { id }
      })
      return done(null, user)
    } catch (error) {
      if (error) {
        logger.error(error)
        return done(error, false)
      }
    }
  })
  local()
  github()
  google()
  kakao()
  facebook()
}

✅ 전략들은 아래그림과 같은 구조로 전략들을 각각 구현하고 그것들을 index에서 호출한다.

라우트

라우트 파트는 로그인버튼 클릭시 호출되는 라우트와 provider로부터 받는 callback을 위한 라우트로 각 전략별로 2개씩이 필요하다.

Passport 흐름

✅ 로컬로그인으로 흐름을 정리하자면 아래와 같다.

  1. 로그인버튼의 의해 로그인진행 라우트로 처음 시작됨.
  2. 라우트경로를 auth/login이라고 할 때 이 라우트의 미들웨어로 passport를 사용하고 이 passport의 전략으로 흐름 진행됨
  3. 해당 사용자가 정상적으로 있는경우 로컬전략의 로직 구현부에서 done(null, user)로 콜백이 발생한다.
    3-1. local전략의 경우에는 req.login() 함수를 호출해야 session에 등록됨(serializeUser 호출됨😀😀)
  4. 이때 serializeUser(user, done)에 user를 콜백하도록 약속되어있음. 이때 이 user의 식별자값을 호출 done(null, user.id) (여기선 id인데 어떤식별자값이던 상관 없음.)

user.id로 할 경우 세션id값이 passport: {user: 아이디값}의 형태로 등록됨.

serializeUser는 즉 로그인에 성공한 user의 식별자를 세션에 등록하는 역할을 하는 것이다.

따라서 로그인이 성공하면 딱 한번호출되는 것이 있다면 이 세션에 등록하는 과정인 serializeUser이다.

  1. 이후 거의 모든 요청에 이 사용자가 로그인에 성공한 사람인지 체크하는 과정인 deserializeUser이 호출된다.

deserializeUserapp.use(passport.session()) 이 미들웨어가 호출하는 것이므로 이것에 아래 있는 라우팅경로에 호출들은 무조건 다 호출된다.

이렇게 호출된 deserializeUser는 세션에 등록된 id값을 통해 user를 DB에서 조회할 수 있다. user가 조회되면 done(null, user)의 콜백을 통해 전달해주고 전달된 user는 req.user 에 등록된다. req.user는 passport에서만 사용가능하다.

따라서 passport를 사용하지 않으면 req.user라는 객체는 없다. (따로 만들어 넣지 않는 이상)

  1. req.session.destroy 함수가 호출되기 전까지는 이 세션을 항상 active한 것이다.
profile
🌃브로콜리한 개발자🌟

0개의 댓글