본문 바로가기

STUDY/React

React | Node.js를 backend로 사용하는 Passport-local 로그인!

React와 Node.js연동은 이전글을 참고하세요

 

Passport.js는 Node.js의 프레임워크인 Express를 기반으로하는 인증 모듈로,
기본 설치 및 사용방법과 Node.js + Express + MySQL + Passport-local 로그인은 아래 링크를 참고하세요.
2020/01/10 - [STUDY/Node.js] - Node.js | Passport.js (passport-local) + MySQL

 

 

 

1. 입력받은 ID와 비밀번호 값 상위 컴포넌트로 전달 (Login → App)

onSubmit메서드를 이용하여 input태그에 입력받은 값을 App컴포넌트로 보낸다.

 

class Login extends Component {
    submitHandler = (event) => {
        event.preventDefault();
        console.log(event.target.id.value, event.target.pwd.value);
        this.props.login_process({
            id: event.target.id.value,
            pwd: event.target.pwd.value
        })
    }
    render () {
        return(
            <div className="loginWrap"> 
                <h1>LOGIN</h1>
                <form onSubmit={this.submitHandler}>
                    <input type="text" name="id" placeholder="ID"></input><br></br>
                    <input type="password" name="pwd" placeholder="PASSWORD"></input><br></br>
                    <input type="submit" value="LOGIN"/>
                </form>
            </div>
        );
    }
}

 

 

 

 

2. fetch를 이용하여 서버로 데이터 전달 (React → Node)

login_process에는 login이라는 함수가 선언되어 있다.

<Login login_process={this.login}></Login>

 

login 함수 내에서는 fetch를 이용하여 서버로 데이터를 전송한다.

이 fetch를 통해 로그인을 시도하는 것

fetch는 JQuery의 AJAX와 사용법이 비슷하기 때문에 AJAX를 사용해보았다면 fetch도 금방 사용할 수 있을 것이다.

fetch에서 사용된 url인 '/users/login'은 Node.js측의 route주소이며, 해당 route내에서 passport로그인을 시도한다.
그래서 반드시 method는 POST로 설정해주어야 함!
login = (login_info) => {
    console.log("App.js login() " + JSON.stringify(login_info))
    fetch('/users/login', {
      method: "post",
      headers: {
        "Content-Type": "application/json; charset=utf-8"
      },
      credentials: "same-origin",
      body: JSON.stringify(login_info)
    })
    .
    .
    .
    
  }

 

// fetch의 기본 문법
fetch('url', options);

 

+) fetch 더 알아보기

 

 

javascript fetch 를 이용한 ajax 통신 및 주의점 - 가리사니

# fetch 란? fetch가 나오기전까진 자바스크립트에서 ajax을 쓰기란 매우 까다롭고 익스플로러랑 기타브라우저가 맞춰저 있지 않다보니 커먼라이브러로 만들어쓰거나 손쉽게 만들어져 있는 제이쿼리를 이용하였습니다. \ ajax이 처음 나오던 시절엔 사이트 전체에 극히 일부에서 사용하던 기술이었지만, 현재는 극단적으로 말해 [SPA](https://en.w...

gs.saro.me

 

Using Fetch

Fetch API를 이용하면 Request나 Response와 같은 HTTP의 파이프라인을 구성하는 요소를 조작하는것이 가능합니다. 또한 fetch() 메서드를 이용하는 것으로 비동기 네트워크 통신을 알기쉽게 기술할 수 있습니다.

developer.mozilla.org

 

 

3. Passport-local Starategy를 이용한 로그인 처리 및 세션 생성(Node.js)

이 부분에서 너무 어렵게 생각하는 바람에 몇 시간을 날렸는데...
Passpot는 route경로가 일치하고, POST방식이면 바로 Passport를 실행된다.
즉, fetch에서 지정한 경로로 post방식으로 데이터를 보내면 그 경로(route)안에 passport인증을 처리하는 부분이 있기만 하면 됨!

 

passport.authenticate('local')가 실행되면서 passport-local방식의 인증이 시도된다.

처리 후 콜백 함수가 실행되고, 로그인이 잘 실행되었다면 req.user로 접근이 가능해진다.

.then을 통해 response로 보낸 값을 React(Client Side)로 받아올 수 있게 되는 것!

router.post('/login', passport.authenticate('local',  { failureRedirect: '/login' }),
function(req, res) {	// 콜백함수
  console.log("req.user : "+ JSON.stringify(req.user));
});

 

 

4. Passport가 생성한 user세션 전달 (Node → React)

로그인에 성공하였을 경우 passport는 user정보를 세션에 저장하며,

request.user를 통해 로그인한 user정보에 접근할 수 있다.

 

passport가 권장하는 기본 문법으로 사용하면 로그인에 실패했을 경우 failureRedirect가 실행되는데,
현재 상황에서는 response를 이용해 다시 fetch쪽으로 데이터를 전달해야 하기 때문에 customCallback을 사용했다.

user(req.user와 동일)를 인자값으로 받고
user정보가 있으면(로그인 성공) user를 json형식으로 변환하여 전송, user정보가 없으면(로그인 실패) 빈 json형식을 전송한다.
// user.js

router.post('/login', function(req, res, next) {
    passport.authenticate('local', function(err, user, info) {
      if (err) { return next(err); }
      
      if(user){ // 로그인 성공
        console.log("req.user : "+ JSON.stringify(user));
        var json = JSON.parse(JSON.stringify(user));
        
        // customCallback 사용시 req.logIn()메서드 필수
        req.logIn(user, function(err) {
            if (err) { return next(err); }
            return res.send(json);
          });

      }else{	// 로그인 실패
        console.log("/login fail!!!");
        res.send([]);
      }
    })(req, res, next);
});

 

 

그리고 React에서는 .then을 이용하여 Node에서 전송한 데이터를 받는다.

login = (login_info) => {
    console.log("App.js login() " + JSON.stringify(login_info))
    fetch('/users/login', {
      method: "post",
      headers: {
        "Content-Type": "application/json; charset=utf-8"
      },
      credentials: "same-origin",
      body: JSON.stringify(login_info)
    })
    .then(res => res.json())
    .then(data => {
      console.log("App.js login .then " , data);
      if(data.EMAIL === undefined){	// 로그인 실패 빈 json형식이 넘어온 경우
        alert('login fail!');
      }
        this.setState({ users : data })
    })
  }

 

 

 

처음 users값을 세팅해주는 부분.

세션의 유효기간이 지나지 않았다면 프론트에서 users값을 계속 받을 수 있음!

(passport의 deserializeUser가 계속 세션이 유효한지 검사해주기 때문에)

 

// React	App.js

componentDidMount() {
    // 프록시로 등록한 서버주소가 생략됨
    fetch('/users')
      .then(res => res.json())
      // json형식으로 받아온 값을 setState를 이용해 값을 재설정해줌
      .then(users => this.setState({ users }));
 }
// Node		users.js

router.get('/', (req, res) => {
    var user_info = null;
    if(!req.user){
        user_info = [];
    }else{
        user_info = JSON.parse(JSON.stringify(req.user));
    }
    res.json(user_info);
})

 

 


 

passport.serializeuser not called 발생... customCallback을 쓰면 여러가지 수정해야 할 부분들이 많다.

serializeUser가 user정보를 세션에 저장하는 부분? 정도라고 이해하고 있는데,

콘솔에 실행순서를 출력해보면 

route에서 로그인 시도 → 성공 → 세션에 user정보 생성(serializeUser실행)의 순서로 이뤄진다.

 

user정보를 인자값으로 받고, user정보가 있으면 done을 통해 user의 고유 식별값이 EMAIL을 세션에 저장

passport.serializeUser(function(user, done) {
    console.log("serializeUser ", user)
    if(user){
        done(null, user.EMAIL);
    }  
});

 

 

 


 

 

deserializeUser가 실행되지 않는 현상이 발생...

passport.deserializeUser never called...

req.isAuthenticated() is always return false....

deserializeUser는 클라이언트 측에서 다른 요청이 있을 때 실행되어서 세션에 담긴 유저 정보를 다시 알려주는 역할을 하는 메서드로 passport에서 가장 중요한 역할을 담당한다고 보면 된다.

그런데 이 메서드가 실행되지 않음. 심지어 로그인도 성공하고 serializeUser까지 실행되는데, 오직 deserializeUser만... 그리고 req.isAuthenticated()도 false값을 반환해준다. 정상적으로 작동하면 true값이 반환되어야 함.

 

 

우선 fetch로 서버측과의 소통을 할 때 옵션으로 반드시 credentails를 설정해줘야 한다.

원래는 same-origin으로 설정했었는데, include로 변경하니 CORS에러 발생.

세션에 요청할 때는 무조건 credentials 설정 필수

fetch('http://localhost:3002/users/login', {
      method: "post",
      headers: {
        "Content-Type": "application/json; charset=utf-8"
      },
      credentials: 'include',
      body: JSON.stringify(login_info)
    })

 

우선 CORS를 설정할 때 옵션값을 넣어주자.

origin은 클라이언트 측의 주소를 넣어주고, credentials의 값을 true로 설정해준다.

const cors = require('cors');
app.use(cors({origin: "http://localhost:3000", credentials:true}));

 

그리고 session의 옵션값도 변경해줬음.

secure를 false로!

(다시 확인해보니 secure:fasle / httpOnly:false 설정해주지 않아도 됨!)

router.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true,
  cookie:{maxAge:30000, 
          secure:false,
          httpOnly:false}
}));

 


 

 

 

실행화면

리액트 처음 실행모습 아직 Users가 비어있음

 

 

로그인에 성공하면 Users정보가 나타남

 

로그인에 실패하면 Users정보는 여전히 없고 alert창 생성

 

 

+)참고

custom Callback 작성하기

 

Documentation: Authenticate

Authenticate Authenticating requests is as simple as calling passport.authenticate() and specifying which strategy to employ. authenticate()'s function signature is standard Connect middleware, which makes it convenient to use as route middleware in Expres

www.passportjs.org