분류 Reactjs

올바른 클라이언트 측 인증 : 쿠키와 로컬 스토리지

컨텐츠 정보

  • 조회 738 (작성일 )

본문

올바른 클라이언트 측 인증 : 쿠키와 로컬 스토리지 


기대 


응용 프로그램에 로그인하면 다음에 브라우저에서 새 탭이나 창을 열 때 해당 응용 프로그램에 계속 로그인 될 것으로 예상됩니다. 즉, 클라이언트 (브라우저)는 어떤 식으로든 모양 또는 형태에 따라 로그인 상태를 유지하기 위해 참조를 유지 해야 합니다.


https://www.taniarascia.com/full-stack-cookies-localstorage-react-express/ 


클라이언트의 상태를 어디에서 유지할 수 있습니까? 


프런트 엔드 응용 프로그램에서 보안 및 인증을 다루는 것은 어려운 문제 일 수 있습니다. 

웹 애플리케이션에서 클라이언트의 상태를 유지 보수하는 데 일반적으로 두 가지 방법이 있습니다.



취약점은 무엇입니까? 


이 두 가지 방법 모두 잠재적 관련 보안 문제와 함께 제공됩니다.


 Method

 취약점

 Local Storage

 XSS - cross-site scripting

 Cookies

 CSRF - cross-site request forgery


  • XSS 취약점으로 인해 침입자는 사이트에 JavaScript를 주입 할 수 있습니다.
  • 공격자는 CSRF 취약점을 통해 인증 된 사용자를 통해 웹 사이트에서 작업을 수행 할 수 있습니다.


이 두 가지 취약점과 그 원인의 차이점에 대한 좋은 입문서는 JWT 저장 위치 – 쿠키 대 HTML5 웹 스토리지에서 찾을 수 있습니다.


어떻게 해결할 수 있습니까? 


브라우저 확장에 있는 것과 같은 타사 스크립트가 로컬 저장소를 악용 할 수 있고 쿠키를 사용하여 인증을 스푸핑 할 수 있는 경우 클라이언트 상태를 어디에 배치 할 수 있습니까?


Auth0 문서에서 쿠키를 사용한 단일 페이지 앱 인증에서 애플리케이션이 다음과 같은 경우


  • 자신의 백엔드를 사용하여 고객에게 제공됩니다.
  • 백엔드와 동일한 도메인
  • 백엔드에 대한 인증이 필요한 API 호출

인증을 위해 쿠키를 안전하게 사용하는 방법이 있습니다.


그것은 어떻게 생겼습니까? 


설정의 실제 예 :


  • 프론트 엔드에 React 단일 페이지 애플리케이션 (SPA)
  • Node + Express 서버 백엔드
  • 웹 쿠키 (보안, HttpOnly, 동일한 사이트)

Express 서버는 / api로 시작하는 경로를 제외한 모든 경로에서 React SPA를 제공합니다. React 애플리케이션은 모든 엔드 포인트에 대해 Express 서버에 충돌합니다. 이 방법을 사용하면 프런트 엔드 앱이 동일한 도메인에 있고 서버가 있으므로 HttpOnly, 보안 및 동일한 사이트 옵션으로 쿠키를 보호 할 수 있습니다.


여기에서 마이크로 서비스 또는 일부 보호 서버에 대한 API 호출을 수행 할 수 있습니다. 실제 API 엔드 포인트 및 액세스 토큰은 브라우저에서 볼 수 없습니다.


아래에서는 실제 아키텍처 연습 없이 전체 스택 애플리케이션을 위해 이 아키텍처를 설정하는 몇 가지 주요 개념을 설명합니다.


전체 스택 TypeScript를 사용하여 이 설정의 실제 예를 보려면 TakeNote의 소스를 확인하십시오.


Express에서 HTTP 쿠키 사용 


Express에서 쿠키를 사용하려면 쿠키 파서 모듈을 사용하십시오.


쿠키 분석 


const cookieParser = require('cookie-parser') app.use(cookieParser()) 


쿠키 설정 


경로에서 몇 가지 중요한 속성을 사용하여 응답 객체에 쿠키를 설정할 수 있습니다.


// Set a cookie response.cookie('nameOfCookie', 'cookieValue', { maxAge: 60 * 60 * 1000, // 1 hour httpOnly: true, secure: true, sameSite: true, }) 


  • Same Site - 사이트 간 요청에서 쿠키가 전송되지 않도록 합니다.
  • HTTP Only - 쿠키는 서버에서만 액세스 할 수 있습니다
  • Secure - 쿠키는 HTTPS를 통해 전송되어야 합니다

쿠키 받기 


쿠키는 이제 후속 응답에서 읽을 수 있습니다.


// Get a cookie response.cookies.nameOfCookie 


쿠키 지우기 


인증에서 로그 아웃 하면 쿠키를 지우고 싶을 것입니다.


// Clear a cookie response.clearCookie('nameOfCookie') 


Express 미들웨어의 로컬 값 


Express는 미들웨어에서 실행됩니다. 하나의 미들웨어에서 쿠키를 업데이트하고 다음 미들웨어에서 쿠키를 사용하려는 경우 Express 로컬로 저장할 수 있습니다. preAuth 라우트에서 JWT 액세스 토큰을 새로 고치고 핸들러에서 해당 인증을 사용하고 마지막에 응답으로 쿠키를 보내야 하는 경우에 유용 할 수 있습니다.


// Create a local const refreshMiddleware = (request, response, next) => { const accessToken = getNewAccessToken(refreshToken) // Set local response.locals.accessToken = accessToken next() } // Use a local const handler = (request, response) => { const updatedAccessToken = response.locals.accessToken } router.post('/app/user', refreshMiddleware, handler) 



프론트 엔드 리 액트 애플리케이션 제공 


이 설정의 좋은 예는 Simple React Full Stack 상용구 설정에서 찾을 수 있습니다. 궁극적으로 응용 프로그램의 레이아웃은 다음과 같습니다.


- dist # Distribution folder of the production React SPA build - src - client # React source files - server # Express server files 


이 경우 서버 파일은 다음과 같습니다.

src/server/index.js
// Initialize Express app
const express = require('express')
const app = express()
const router = require('./router')

// Serve all static files from the dist folder
app.use(express.static(path.join(__dirname, '../../dist/')))

// Set up express router to serve all api routes (more on this below)
app.use('/api', router)

// Serve any other file as the distribution index.html
app.get('*', (request, response) => {
  response.sendFile(path.join(__dirname, '../../dist/index.html'))
})

Express Routes and Handlers 


Express Router 클래스를 사용하면 모든 API 라우트를 서브 디렉토리로 구성하고 기본 서버 진입 점에서 한 줄로 가져올 수 있습니다.

- src - server - router - handlers - index.js 


경로는 모두 개별 하위 디렉토리로 구성 될 수 있습니다.


src/server/routes/index.js
const router = require('express').Router()
const bookRoutes = require('./books')
const authorRoutes = require('./authors')

router.use('/books', bookRoutes)
router.use('/authors', authorRoutes)

module.exports = router

라우터는 / api를 사용하고 저자는 / api / authors / jk-rowling에 대한 GET API 호출 인 / authors를 사용하기 때문에 하나의 경로 집합에서 모든 GET, POST, DELETE 경로 등을 정의 할 수 있습니다. 이 예제에서 getAuthor 핸들러를 호출합니다.

src/server/routes/authors.js
const router = require('express').Router()
const authorHandlers = require('../handlers/authors')

// Get
router.get('/', authorHandlers.getAllAuthors)
router.get('/:author', authorHandlers.getAuthor)

// Post
router.post('/', authorHandlers.addAuthor)

module.exports = router

모든 관련 작성자 핸들러를 handlers 서브 디렉토리에 넣을 수 있습니다.


src/server/handlers/authors.js
module.exports = {
  getAllAuthors: async (request, response) => {
    // Some logic...
    if (success) {
      response.status(200).send(authors)
    } else {
      response.status(400).send({ message: 'Something went wrong' })
    }
  },
  addAuthor: async (request, response) => { ... },
}

그러면 서버 진입 점으로 돌아가서 / api의 모든 경로를 가져옵니다.


src/server/index.js
// Set up all API routes
const router = require('./router')

// Use all API routes
app.use('/api', router)

React 단일 페이지 애플리케이션 


Tyler McGinnis는 React Router를 사용한 보호 된 경로 및 인증에 대한 훌륭한 기사를 제공하며 PrivateRoute 및 PublicRoute 구성 요소를 만드는 방법을 보여줍니다.


이는 프런트 엔드 전용 인증 보호이며 민감한 데이터를 보호하기 위해 신뢰할 수 없습니다. 응답을 반환하기 위해 액세스 토큰 (또는 보안 방법)이 필요한 백엔드 API로 보호해야 합니다.


앞에서 언급 한 기사의 기본 경로 예를 사용하여 React에서 Express 서버로 API 호출을 수행하고 일부 글로벌 컨텍스트 상태를 인증하고 프론트 엔드를 통해 앱을 라우팅 하는 방법이 있습니다.


App.js
import React, { Component } from 'react'
import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom'
import axios from 'axios'
// ...plus page and context imports

export default class App extends Component {
  static contextType = AuthContext

  state = { loading: true }

  async componentDidMount() {
    const Auth = this.context

    try {
      const response = await axios('/api/auth')

      Auth.authenticate()
    } catch (error) {
      console.log(error)
    } finally {
      this.setState({ loading: false })
    }
  }

  render() {
    const Auth = this.context
    const { loading } = this.state

    if (loading) {
      return <div>Loading...</div>
    }

    return (
      <Router>
        <Switch>
          <PublicRoute exact path="/login" component={LoginPage} />
          <ProtectedRoute exact path="/dashboard" component={DashboardPage} />
          <Route exact path="/logout" component={LogoutPage} />
          <Redirect to="/login" />
        </Switch>
      </Router>
    )
  }
}

이제 개발 서버는 인증 상태에 따라 올바른 경로로 안내합니다. 프로덕션 모드에서는 배포 index.html 파일이 제공됩니다. 자세한 내용은 아래를 참조하십시오.


서비스 및 개발 


프로덕션 설정을 사용하면 전체 React 애플리케이션이 배포 용으로 구축되며 Express 앱은 모든 경로에서 SPA를 제공합니다.


package.json
// Production
{
  "build": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js",
  "start": "npm run build && node src/server/index.js"
}

개발이 번거롭습니다. 개발을 처리하는 가장 좋은 방법은 정기적으로 하는 것처럼 Webpack dev 서버에서 React를 제공하고 모든 API 요청을 Express 서버에 프록시 하는 것입니다.


package.json
// Development
{
  "client": "cross-env NODE_ENV=development webpack-dev-server --config config/webpack.dev.js",
  "server": "nodemon src/server/index.js",
  "dev": "concurrently \"npm run server\" \"npm run client\""
}

포트 3000의 React 앱과 5000의 서버를 개발 웹 서비스 구성 파일에서 설정할 수 있습니다.


devServer: { historyApiFallback: true, proxy: { '/api': 'http://localhost:5000', }, open: true, compress: true, hot: true, port: 3000, } 

히스토리 설정하기 ApiFallback은 SPA 경로가 올바르게 작동하도록 합니다. 프로덕션의 라우트가 루트에서 번들을 제공하도록 하려면 Webpack의 publicPath를 /로 설정해야 합니다.


Webpack Boilerplate는 Webpack을 설정하는 방법에 사용하기에 좋은 예입니다 (이 경우에는 직접 건물에서 src로, 건물에서 src / client로 모든 것을 옮길 것입니다).


결론 


이 리소스가 영구 클라이언트 측 스토리지 (XSS 및 CSRF)와 관련된 다양한 유형의 취약점과 HttpOnly, SameSite, Secure Web Cookies와 같은 잠재적 공격을 완화하기 위해 취할 수 있는 몇 가지 접근 방식을 이해하는 데 도움이 되었기를 바랍니다.