AngularJS, Passport로 인증구현

AngularJS를 사용하는 프로젝트에서 사용자 인증을 구현해 보자. 작업환경은 아래와 같다.

  • 프레임웍: Angular-fullstack (프론트 엔드: AngularJS, 백엔드: ExpressJS)

백엔드 구현

노드 익스프레스에서는 패스포트 모듈로 인증을 쉽게 구현할 수 있다. 로그인 정보는 서버측 세션 메모리에 저장할 것이고 /api/auth (get/post/delete) 프로토콜로 프론트엔드와 통신할 것이다.

우선 패스포트 모듈을 익스프레스과 연동해야 한다. 앵귤러풀스택에서는 app.js 에서 익스프레스 객체를 생성하여 별도로 구현한 config/express.js 모듈에 넘겨준다. express.js에서는 서버 정보를 설정하는 작업을 한다. 마찬가지로 필자도 config/passport.js 모듈을 작성하고 여기에서 패스포트 관련한 설정 작업을 할 것이다.

var passport = require("passport")
var LocalStrategy = require("passport-local").Strategy

exports = module.exports = function (app) {
  app.use(passport.initialize())
  app.use(passport.session())

  // 로그인 라우팅시 미들웨어로 수행됨
  // 유저네임과 비밀번호를 체크함
  passport.use(
    new LocalStrategy({}, function (username, password, done) {
      console.log("passport.use()", username, password)

      if (
        username.toUpperCase() !== "user01" ||
        password.toUpperCase() !== "password01"
      ) {
        return done(null, false)
      }
      return done(null, { user: username, password: password })
    })
  )

  // 인증 성공후 세션에 데이터 저장시 호출됨
  passport.serializeUser(function (user, done) {
    console.log("serializeUser()", user)
    done(null, user)
  })

  // 세션에 저장된 데이터 조회시 호출됨
  passport.deserializeUser(function (user, done) {
    console.log("deserializeUser()", user)
    done(null, user)
  })
}

세션 메모리를 패스포트 데이터 저장소로 사용했기 때문에 익스프레스에 세션 모듈을 연동해야 한다. express-seesion 모듈을 아래와 같이 연결한다.

var session = require('express-session');

module.exports = function (app) {

  // ..

  app.use(session({
      name: 'MY_APP',
      secret: 'MY_S3CR3T'
    }));

  // ..

이것으로 익스프레스와 패스포트 모듈을 연동했다. 이젠 백엔드 프로토콜을 작성해 보자. yo angular-fullstack:endpoint auth 로 백엔드 api 스패폴딩 파일을 자동을 생성한다. api/auth/index.js 파일에 아래의 세가지 프로토콜을 작성한다.

  • `/api/auth (GET)`: 세션을 체크하여 인증여부 확인
  • `/api/auth (POST)`: 인증 수행후 로그인 정보를 세션에 저장
  • `/api/auth (DELETE)`: 세션 정보를 삭제하여 로그아웃 수행
"use strict"

var express = require("express")
var passport = require("passport")

var router = express.Router()

// /api/auth (get)
router.get("/", function (req, res) {
  res.json(req.user) // 세션에 저장된 로그인 정보를 반환
})

// /api/auth (post)
router.post("/", passport.authenticate("local"), function (req, res) {
  // config/passport.js에서 설정한 패스포트 미들웨어 로직이 수행된다.
  // 인증을 성공하면 200코드를 반환, 실패시 401, Unauthorized를 자동으로 반환한다.
  res.send(200)
})

// /api/auth (delete)
router.delete("/", function (req, res) {
  req.logout() // 세션 정보 삭제
  res.send(200)
})

module.exports = router

 

프로트엔드 구현

인증이 필요한 페이지로 라우팅할때 promise를 사용한다. ui-route (혹은 ng-route)로 라우팅 설정할때 resove 속성을 추가할 수 있다. 이때 /api/auth (GET)으로 로그인 여부를 백엔드에서 확인하고 인증된 경우뫈 라우팅을 허용한다. 그렇지 않은 경우는 로그인 페이지(/login)로 리다이렉팅 한다.

"use strict"

angular.module("myApp").config(function ($stateProvider) {
  $stateProvider.state("main", {
    url: "/",
    templateUrl: "app/main/main.html",
    controller: "MainCtrl",

    // promise를 사용한 부분.
    // resolve가 정상 수행되어야함 '/'으로 라우팅을 허용한다.
    resolve: {
      isAuth: function ($http, $state) {
        // 백엔드에 인증여부를 확인하다.
        return $http({ method: "GET", url: "/api/auth" }).error(function (
          data,
          status
        ) {
          console.log(data, status)

          // 인증되지 않은 경우 로그인 페이지로 이동.
          $state.go("login")
        })
      },
    },
  })
})

로그인 페이지는 /login 으로 라우팅하고, 로그인 템플릿과 컨트롤러를 가각 연결한다. 컨트롤러에서는 /api/auth (POST) 프로토콜로 인증을 시도한다. 인증 성공후 메인페이지(/)로 라우팅하고 그렇지 않을 경우 로그인 페이지 (/login)에서 인증 정보를 재 입력 받도록 한다.

"use strict"

angular
  .module("myApp")
  .controller("LoginCtrl", function ($scope, $http, $location) {
    // 로그인 버튼 핸들러
    $scope.login = function () {
      $scope.msg = ""

      // 백엔드에 인증을 시도한다.
      $http
        .post("/api/auth", {
          username: $scope.username,
          password: $scope.password,
        })
        .success(function (data, status) {
          console.log(data, status)

          // 인증 성공시 메인페이지('/')로 이동
          $location.path("/")
        })
        .error(function (data, status) {
          console.log(data, status)

          // 인증 실패시 재로그인을 안내한다.
          $scope.msg = "로그인 정보 오류: 로그인 정보를 다시 입력하세요."
        })
    }
  })

각 페이지에는 로그아웃 버튼을 배치한다. 로그아웃 버튼 클리시 바로 백엔드 프로토콜인 /api/auth (DELETE)를 호출하여 서버의 세션 메모리에 저장된 유저 정보를 삭제한다. 삭제후 반환되는 값을 확인하고 다시 메인 페이지(/)로 라우팅한다. 메인페이지에서는 세션 여부를 체크하고 다시 로그인 페이지로 리다이렉팅 된다.

"use strict"

angular
  .module("myApp")
  .controller("NavbarCtrl", function ($scope, $location, $http, $state) {
    // 로그아웃 버튼 핸들러
    $scope.logout = function () {
      // 백엔드에 로그아웃을 요청한다.
      $http.delete("/api/auth").success(function (data, status) {
        console.log(data, status)

        // 로그아웃 성공시 메인페이지로 이동 ('/')
        if (status === 200) {
          $state.go("/")
        }
      })
    }
  })