MEAN Stack Front to Back (MEANAuthApp) 4. API Authentication and Token
목차 1. 환경 구축 2. Express Setup & Routes 3. User Model and Register 4. API Authentication and Token 5. Angular 4 Components & Routes 6. Register Component, Validation & Flash messages 7. Auth Service & User Registration 8. Login & Logout 9. Protected Requests & Auth Guard 10. App Deployment to Heroku
이번 장에서 할 일 인증에 대한 개념 소개 패스포트 로그인 특정 루트 보호 토큰 인증
초기인증 vs. 인증유지 초기인증(initial login) 기술 인증유지(keeping logged in) 기술 처음 접속시의 사용자 신분 인증 ID/pass, 인증서, 고유주소, Biometrics, 멀티팩터 인증 엄밀한 인증 절차 필요 사용자 계정 DB 확인 필요 인증유지(keeping logged in) 기술 초기 인증 완료된 사용자의 인증상태를 유지하는 기술 쿠키, 세션, 토큰 등 무상태 서비스 (stateless service) 요구 사용자 계정 DB 확인 필요없는 기술 많은 사용자를 위한 확장성, 효율성이 있는 기술 필요
Passport 패스포트란? Node.js를 위한 인증 미들웨어 http://passportjs.org/ 다양한 인증정책(strategy) 적용 가능
Passport Strategy 300개 이상의 인증 정책(Strategy) 로컬인증 페이스북 인증, 트위터 인증, 구글 인증 OAuth 인증
OAuth OAuth란? OAuth = Authentication + Authorization 인증 및 권한관리 프레임워크 https://oauth.net/
JSON Web Token (JWT) JWT란? 토큰 인증을 위한 표준 기술 https://jwt.io/
JSON Web Token (JWT) JWT란? open, industry standard RFC 7519 method for representing claims securely between two parties. a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA.
JSON Web Token (JWT) 토큰 발급 절차 Server’s secret
JSON Web Token (JWT) - 토큰 디코딩 및 정보 획득 가능 - 서명은 서버 비밀값을 이용한 HMAC 계산으로 서버만 계산 가능 Server’s secret
패스포트 로그인 적용 App.js에 패스포트 미들웨어 추가 // Body Parser Middleware app.use(bodyParser.json()); // Passport Middleware app.use(passport.initialize()); app.use(passport.session()); app.use('/users', users);
패스포트 로그인 적용 Passport-jwt 사용 Config 폴더에 passport.js 파일 작성 https://www.npmjs.com/package/passport-jwt 참조 A Passport strategy for authenticating with a JSON Web Token JWT 인증을 사용하기 위한 패스포트 정책 > npm install passport-jwt Config 폴더에 passport.js 파일 작성 Passport 관련 기능을 별도의 파일로 관리
config/passport.js 파일 작성 const JwtStrategy = require('passport-jwt').Strategy; const ExtractJwt = require('passport-jwt').ExtractJwt; const User = require('../models/user'); const config = require('../config/database'); module.exports = function(passport){ let opts = {}; opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); opts.secretOrKey = config.secret; passport.use(new JwtStrategy(opts, (jwt_payload, done) => { User.getUserById(jwt_payload. _doc._id, (err, user) => { if (err) { return done(err, false); } if (user) { return done(null, user); } else { return done(null, false); }); })); 옵션을 작성 - 토큰은 AuthHeader에서 Bearer token으로 전송 - 토큰생성을 위한 비밀키 설정 패스포트에 JWT strategy 적용 에러 발생시 에러 출력 인증에 성공하면 user 정보 출력 https://www.npmjs.com/package/passport-jwt 참조
토큰 전송 토큰은 여러 가지 방법으로 보낼 수 있음
패스포트 로그인 App.js에서 passport.js를 읽어옴 // Body Parser Middleware app.use(bodyParser.json()); // Passport Middleware app.use(passport.initialize()); app.use(passport.session()); require('./config/passport')(passport); app.use('/users', users);
인증(/authenticate) 기능 추가 router.post('/authenticate', (req, res, next) => { const username = req.body.username; const password = req.body.password; User.getUserByUsername(username, (err, user) => { if(err) throw err; if(!user) { return res.json({success: false, msg: 'User not found'}); } User.comparePassword(password, user.password, (err, isMatch) => { if(isMatch) { const token = jwt.sign(user, config.secret, { expiresIn: 604800 // 1 week }); res.json({ success: true, token: 'JWT '+token, user: { // 패스워드를 제외한 나머지 필요한 정보를 리턴 id: user._id, name: user.name, username: user.username, email: user.email } else { return res.json({success: false, msg: 'Wrong password'}); routes/users.js 파일에 router.post('/authenticate’,…) 기능 추가 사용자가 입력하는 로그인 정보를 변수로 할당 username으로 DB를 검색 입력 패스워드와 DB에서 읽어온 암호화된 패스워드 정보를 비교 (함수를 아직 만들지 않았음) 패스워드가 일치하면 토큰을 생성 서버비밀정보 이용, 유효기간 지정 토큰과 사용자 정보를 브라우저에 리턴 패스워드를 제외한 정보를 보내기 위해 User객체를 재구성함 (패스워드 제외)
인증(/authenticate) 기능 추가 Config.secret 변수 사용을 위해 routes/users.js 파일에 다음 선언을 추가해야 함 const config = require('../config/database');
models/user.js에 comparePassword 함수 추가 module.exports.addUser = function(newUser, callback){ bcrypt.genSalt(10, (err, salt) => { bcrypt.hash(newUser.password, salt, (err, hash) => { if(err) throw err; newUser.password = hash; newUser.save(callback); }); } module.exports.comparePassword = function(candidatePassword, hash, callback){ bcrypt.compare(candidatePassword, hash, (err, isMatch)=>{ callback(null, isMatch); https://www.npmjs.com/package/bcrypt 참조
Postman으로 로그인 테스트 (1) 새로운 탭 열기 (3) Send (4) 서버의 응답 토큰이 생성되어 발급받았음 사용자 정보 받음 (패스워드 제외) (2) 위 주소로 POST 선택 Headers 정보 입력 Body/raw 로 username/password 입력
특정 루트 보호 특정 루트는 로그인 이후에만 사용 가능하도록 보호 프로필 루트는 토큰을 이용하여 접근하도록 설정 http://localhost:3000/users/profile 로그인 안된 상태에서는(토큰이 없으면) 접근 금지 routes/users.js 파일에서 프로필 보기 부분을 수정 // Profile router.get('/profile', passport.authenticate('jwt', {session:false}), (req, res, next) => { res.json({user: req.user}); }); 토큰인증 된 경우에만 서버가 응답한 user 데이터를 보여줌
Postman을 이용한 테스트 앞의 로그인 결과에서 Token 부분을 복사
Postman을 이용한 테스트 (1) GET (2) 프로필 주소 (4) Send (3) Headers의 Authorization 값에 앞에서 복사한 토큰을 붙여넣기
Postman을 이용한 테스트 토큰으로 로그인 성공