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("/")
}
})
}
})