어서와, 개발은 처음이지?

RESTful API에 Express Session 사용하기 본문

Node

RESTful API에 Express Session 사용하기

오지고지리고알파고포켓몬고 2018. 9. 4. 20:59
반응형


 Session 


세션은 클라이언트-서버 간 인증 정보를 기록하여, 특정 시간동안 인증절차 없이 사용자를 신뢰하고 서비스 이용에 편의와 여러가지 정보를 제공하는데에 사용됩니다. 

세션이 생성되면 세션을 식별할 수 있는 id가 쿠키형태로 클라이언트에 저장되고, 서버에 요청할 때 이 id를 같이 전송합니다.

서버는 이 id로 각 클라이언트를 식별하고, 해당 클라이언트의 세션을 사용합니다.


<세션 생성 전>


<세션 생성 후>



 Express Session 


express-session은 express.js에서 세션관리 기능을 제공하는 미들웨어입니다.

간단한 사용법은 아래와 같습니다.

"use strict";

const express = require('express');
const session = require('express-session');
const app = express();

app.use(session({
  secret: 'asdf123',
  resave: false,
  saveUninitialized: true,
})); 

app.get('/', (req, res) => {
  res.send('hi '+req.session.name);
});

app.get('/login', (req, res) => {
  req.session.name = 'yuddomack';
  req.session.save(() => {
    res.redirect('/');
  })
});

app.listen(3000,function(){
  console.log(`Connect 3000 port!`)
});

app.use(session())을 통해 req.session이라는 객체를 사용할 수 있으며, secret은 세션을 암호화 하기위한 키 값으로 임의의 값을 설정해주시면 됩니다. resave와 saveUninitialized는 크게 중요하지 않으니 기본 값을 사용합니다.


res.session에 key value를 달아주는 것으로 세션에 정보를 기록할 수 있습니다.


루트(/) 라우트에 접속했을때 req.session.name은 undefined를 나타내지만 /login 라우트에 접속하면 세션에 name이 생성되고, 클라이언트를 식별할 수 있는 id를 발행하여 쿠키로 response 해줍니다.




 RESTful API와 Session 


Express Session을 사용하던 중 API 요청에도 세션이 적용될까 하는 궁금증이 생겼습니다.

그 궁금증을 해결하기 위해 


1.URL을 통한 평범한 요청

2.브라우저에서 fetch를 사용한 API 요청

3.React Native Application에서 fetch를 사용한 API 요청


상황을 테스트 해봤습니다.

일단 다음과 같이 라우트를 개설했습니다.

app.get('/login/browser', (req, res) => {
  req.session.name = 'browser man';
  req.session.save(() => {
    res.send({result : 'hi '+req.session.name});
  })
});

app.get('/login/api', (req, res) => {
  req.session.name = 'api man';
  req.session.save(() => {
    res.send({result : 'hi '+req.session.name});
  })
});

app.get('/login/react_native', (req, res) => {
  req.session.name = 'react man';
  req.session.save(() => {
    res.send({result : 'hi '+req.session.name});
  })
});

app.get('/buy', (req, res) => {
  if(req.session.name){
    res.send({result:'thank you '+req.session.name});
  } else {
    res.send({result:'login please'});
  }
});

<app.js>

1~3의 케이스가 정상 동작한다면, /buy를 요청했을때, thank you 라는 메세지가 나올 것 입니다.



1.URL을 통한 평범한 요청


보통의 웹 서비스처럼 브라우저에서 URL을 사용하여 서비스에 접근해봤습니다.

 


브라우저에서 URL을 통한 접근은 이상없이 동작하는 모습을 볼 수 있었습니다.



2.브라우저에서 fetch를 사용한 API 요청(부제 : express에서 크로스도메인 Access-Control-Allow-Origin 해결하기)


이번에는 html 파일을 만들어서 fetch를 상용하여 API로 세션을 생성해보겠습니다.

사실 이 Ajax라던지 이런 비동기 요청을 했을때, 쿠키가 생성되는지에 대한 의문이 들어서 2번 케이스를 실험하게 되었습니다.

추후에 react로 웹을 구현할 계획인데 만약 이 부분이 해결된다면 굳이 react route를 사용할 필요가 있을까 싶었습니다.


우선 html 파일을 아래와 같이 제작했습니다.

fetch는 localhost에서 동작 안하는 경우가 있어서 로컬 ip를 직접 입력했습니다.

<script>
function reqLogin(){
  fetch('http://192.168.xxx.xxx:3000/login/api')
  .then((response) => response.json())
  .then((responseJson) => {
    resLogin.innerHTML = JSON.stringify(responseJson);
  })
  .catch((error) => {
    resLogin.innerHTML = error;
  });
}

function reqBuy(){
  fetch('http://192.168.xxx.xxx:3000/buy')
  .then((response) => response.json())
  .then((responseJson) => {
    resBuy.innerHTML = JSON.stringify(responseJson);
  })
  .catch((error) => {
    resBuy.innerHTML = error;
  });
}
</script>

<body>
<button onclick="reqLogin()">로그인 요청</button>
<p id="resLogin">결과</p>
<button onclick="reqBuy()">구매 요청</button>
<p id="resBuy">결과</p>
</body>

<api.html>


html 페이지를 실행하고 버튼을 눌러서 api를 요청하자, 장고로 api 만들때 보던 낯익은 메세지(Access-Control-Allow-Origin)가 보였습니다.



이를 해결하기 위해 cors 모듈을 설치하고 express에서 사용해주도록 했습니다.

const cors = require('cors');
const corsOptions = {
    origin: true,
    credentials: true
};

app.use(cors(corsOptions));

<app.js>

그리고 다시 요청해보았습니다.



이번에는 로그인 요청(/login/api) 후 세션 id(connect.sid 쿠키)가 정상적으로 생성된 모습을 볼 수 있었습니다.

하지만 구매 요청(/buy)를 했을 때, thank you 메세지가 아닌 login please라는 결과를 보여줍니다.


무엇이 문제였을까요?


fetch를 통해 request할 때 이 쿠키 정보가 함께 전송되지 않아서 서버 입장에서는 식별자 정보를 가지고 있지 않은 사용자로 인식하기 때문이였습니다.

이를 해결하기 위해 fetch에 header정보를 설정해줍니다.

<script>
const header ={
  method: "GET",
  headers: {
    'Accept':  'application/json',
     'Content-Type': 'application/json',
     'Cache': 'no-cache'
  },
  credentials: 'include'
}; 
function reqLogin(){
  fetch('http://192.168.219.113:3000/login/api', header)
  // 생략
}
</script>

<api.html>

핵심은 credentials: 'include' 입니다.

include: Always send user credentials (cookies, basic http auth, etc..), even for cross-origin calls.라고 모질라 형님들이 말씀하셨습니다.


이제 정상적으로 동작하는 것을 볼 수 있습니다.




3.React Native Application에서 fetch를 사용한 API 요청


2번 케이스를 무사히 통과하자 React Native도 무난히 성공하였습니다.

아래는 테스트용 React Native 앱의 App.js 코드입니다.

import React, {Component} from 'react';
import {StyleSheet, Text, View, Button} from 'react-native';


type Props = {};
const header ={
  method: "GET",
  headers: {
    'Accept':  'application/json',
     'Content-Type': 'application/json',
     'Cache': 'no-cache'
  },
  credentials: 'include'
};
export default class App extends Component<Props> {
  constructor(props){
    super(props);
    this.state = {
      login:"",
      buy:"",
    };
  }

  _reqLogin(){
    fetch('http://192.168.xxx.xxx:3000/login/react_native', header)
    .then((response) => response.json())
    .then((responseJson) => {
      this.setState((prevState, props) => {
        return {login:JSON.stringify(responseJson)}
      })
    })
    .catch((error) => {
      this.setState((prevState, props) => {
        return {login:JSON.stringify(error)}
      })
    });
  }

  _reqBuy(){
    fetch('http://192.168.xxx.xxx:3000/buy', header)
    .then((response) => response.json())
    .then((responseJson) => {
      this.setState((prevState, props) => {
        return {buy:JSON.stringify(responseJson)}
      })
    })
    .catch((error) => {
      this.setState((prevState, props) => {
        return {buy:JSON.stringify(error)}
      })
    });
  }

  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>Session Test</Text>
        <Button title="로그인 요청" onPress={this._reqLogin.bind(this)}/>
        <Text>{this.state.login || "결과"}</Text>
        <Button title="구매 요청" onPress={this._reqBuy.bind(this)}/>
        <Text>{this.state.buy || "결과"}</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
});


API 요청과 세션이 정상적으로 동작하는 모습을 볼 수 있습니다.


 마무리 


기존의 웹 서비스 처럼 브라우저로 접근하는 요청 뿐만아니라 API 에서도 세션이 사용되는 것을 알 수 있었습니다.

다만, 인증 정보를 유지하기 위해서는 cors 모듈과 credentials 설정을 통해 크로스 도메인 문제와 쿠키 정보를 전달하도록 해야 합니다.


이로써 비동기 API 요청에서도 세션 검증을 통해 정보 제공을 제한할 수 있겠습니다.


Comments