Hapi 인증

이번에는 Hapi Api 서버의 인증 기능을 구현해 보자. hapi-auth-cookie 모듈을 이용하여 세션 쿠키를 이용하여 인증을 구현할 것이다. 인증 정보를 세션에 저장하고 클라이언트와는 쿠키를 통해 인증상태를 통신하도록 구현한다.

세션 인증 활성화

Hapi에는 서버객체의 register() 함수를 통해 플러그인을 등록할 수 있다. Hapi에서의 플러그인은 익스프레스의 미들웨어와 비슷한 개념이다. 세션 인증 모듈 hapi-auth-cookie를 등록하기위해 server.register() 함수를 사용한다.

// Hapi 서버 객체를 이용해 인증 설정을 한다.
module.exports = function (server) {
  // 인증 모듈을 등록한다.
  server.register(require("hapi-auth-cookie"), function (err) {
    if (err) {
      throw err
    }

    // 인증 strategy 를 생성한다.
    server.auth.strategy("mySessionStrategy", "cookie", {
      password: "secret",
      cookie: "sid-example",
      redirectTo: false,
      isSecure: false,
    })
  })
}

인증에는 schemestrategy라는 두 가지 개념이 등장한다. scheme은 hapi-auth-cookie 모듈을 통해 생성되는데 서버의 인증 방법을 설정하는 것이다. hapi에서는 scheme별로 추가적인 모듈을 제공한다.

이외에도 서드파티에서 제공하는 shceme 모듈이 있다.

strategy는 서버객체의 server.auth.strategy() 함수로 생성하는데 세부적인 인증 정책에 대한 정의라고 할 수 있다. 위 샘플코드의 경우 세션쿠키에 대해 비밀번호 설정, 쿠키명 설정, 리타이렉트 정책 설정, secure 설정을 정의하고 있다.

세션 플러그인을 설정한 Hapi 서버 객체는 request 객체를 통해 세션을 시작하고 종료할 수 있다.

  • request.auth.session.set(): 세션 시작
  • request.auth.session.clear(): 세션 종료

이 두 함수를 이용해 세션 시작/종료 함수를 구현한 것이 아래 코드다.

exports.startup = function (auth, data) {
  // 사전 인증정보가 없을 경우 세션에 인증정보 저장
  if (!auth.isAuthenticated) {
    auth.session.set(data)
  }
}

exports.teardown = function (auth) {
  // 세션정보 삭제
  auth.session.clear()
}

이상 Hapi 서버에 세션 쿠키 인증을 위한 환경설정을 마쳤다. 이제는 실제 인증을 위한 프로토콜일 login, logout을 구현해 보자.

라우팅 설정

프로토콜은 최대한 RESTful 하게 작성하기 위해 아래와 같이 구현한다.

  • /auth (post): 로그인, 인증 파라메터는 payload로 받는다. 인증성공시 201, 실패시 403코드를 반환한다.
  • /auth (delete): 로그아웃
  • /auth (get): 테스트용 프로토콜. 세션 정보를 확인한다.

우선 위 프로토콜에 대한 라우팅 설정하는 부분이다. route() 함수 설정 객체의 속성중 config.auth 키를 통해 인증 정책을 설정한다. mode는 'try', 'required' 두 가지 타입을 설정할 수 있다. 로그인의 경우 'try'를 설정하고 인증된 상태에서 접근할수 있는 프로토콜은 'required'를 설정한다. strategy는 server.auth.strategy() 함수로 생성한 strategy를 문자열로 설정한다.

module.exports = function (server) {
  // 로그인
  server.route({
    method: "POST",
    path: "/auth",
    handler: ctrl.login,
    config: {
      auth: {
        mode: "try", // 인증 시도
        strategy: "mySessionStrategy", // 사용할 strategy
      },
    },
  })

  // 로그아웃
  server.route({
    method: "DELETE",
    path: "/auth",
    handler: ctrl.logout,
    config: {
      auth: {
        mode: "required", // 인증 필수
        strategy: "mySessionStrategy",
      },
    },
  })

  // 세션 확인 (개발용)
  server.route({
    method: "GET",
    path: "/auth",
    handler: ctrl.find,
    config: {
      auth: {
        mode: "required",
        strategy: "mySessionStrategy",
      },
    },
  })
}

컨트롤러 로직

로그인 시나리오는 이렇다. /auth (post) 프로토콜을 통해 인증 시도 → 인증 정보 검증 → 검증에 통과하면 세션에 유저정보 저장 → 클라이언트에 인증 성공 정보를 응답. 아래 코드와 주석을 같이 살펴보자.

'use strict';

// 위에서 구현한 세션 모듈. 이것을 통해서 세션 시작/종료 할 수 있다.
var session = require('../../components/session');

exports.login = function (req, reply) {
  var user = {
    username: 'Chris',
    password: 'chrisPass'
  };

  // 인증 정보 검증
  if (user.username !== req.payload.username || user.password !== req.payload.password) {
    return reply(403);
  }

  // 세션 시작, 유저정보 저장
  session.startup(req.auth, user);

  // 인증 성공 정보를 응답
  reply(user).code(201);
};</pre>

로그아웃은 세션을 삭제하고 응답한다. find는 세션 확인용이다.

<pre class="lang:js decode:true " title="routes/auth/auth.ctrl.js">exports.logout = function (req, reply) {

  // 세선 종료
  session.teardown(req.auth);

  // 응답
  reply();
};

// 세션 확인용
exports.find = function (req, reply) {
  reply(req.auth);
};

이상으로 로그인/로그아웃 프로토콜을 구현했다. 실제 로그인, 로그아웃 프로토콜 결과를 첨부한다.

로그인:

login

세션확인:

get-auth

로그아웃:

logout

 

전체 코드: https://github.com/jeonghwan-kim/hapi_study/tree/08_auth구현