Next.js: ๋กœ๊ทธ์ธ/์ธ์ฆ ์˜ˆ์‹œ ์ถ”๊ฐ€

์— ๋งŒ๋“  2016๋…„ 10์›” 29์ผ  ยท  208์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: vercel/next.js

์™€ ํ•จ๊ป˜:

  • ํŽ˜์ด์ง€์—์„œ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ธ์ฆ ๋„์šฐ๋ฏธ
  • ํƒญ ๊ฐ„ ์„ธ์…˜ ๋™๊ธฐํ™”
  • now.sh ์—์„œ ํ˜ธ์ŠคํŒ…๋˜๋Š” ๊ฐ„๋‹จํ•œ ์•”ํ˜ธ ์—†๋Š” ์ด๋ฉ”์ผ ๋ฐฑ์—”๋“œ

๋งŽ์€ ์ƒˆ๋‚ด๊ธฐ๋“ค์—๊ฒŒ ํฐ ๋„์›€์ด ๋  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

๊ทธ๋ž˜์„œ ๋‚˜๋Š” auth๊ฐ€ ์ˆ˜์˜์„ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๊ณณ์—์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด ํด๋ผ์ด์–ธํŠธ ์ธก ์ „์šฉ์ด๋ฉฐ ๊ถ๊ทน์ ์œผ๋กœ ์ „ํˆฌ์˜ ์ ˆ๋ฐ˜์— ๋ถˆ๊ณผํ•ฉ๋‹ˆ๋‹ค.

"๋งค์šฐ ์•ˆ์ „ํ•œ"

php์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Next์˜ ์›์ž ๋‹จ์œ„๋Š” ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค. ๊ฐ€์žฅ ๋ฉ‹์ง„ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜๋Š” ์š”์ฒญ๋œ ๊ฒฝ์šฐ์—๋งŒ ๊ฐ ํŽ˜์ด์ง€๋ฅผ ์ง€์—ฐ ๋กœ๋“œํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ์ธก ์ธ์ฆ๋งŒ ์žˆ์ง€๋งŒ ์„œ๋ฒ„ ๋ Œ๋”๋ง์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ณดํ˜ธ๋œ ํŽ˜์ด์ง€์˜ js๊ฐ€ ์‹ค์ œ๋กœ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋‹ค์šด๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค. ์•ž์œผ๋กœ Next๊ฐ€ ์„œ๋ฒ„ ์›Œํฌํ”Œ๋กœ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง ๋ฐ ๋ฆฌ๋””๋ ‰์…˜์„ ์ฐจ๋‹จํ•˜์—ฌ ์ด๋ฅผ ์™„์ „ํžˆ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—๋Š” ์ฟ ํ‚ค, ์„ธ์…˜ ๋ฐ AFAIK ์„ธ์…˜ ์ €์žฅ์†Œ๊ฐ€ ํ•„์š”ํ•˜์ง€๋งŒ ์ด๋Š” ์ด๋Ÿฌํ•œ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์•ฑ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐ ๋“œ๋Š” ๋น„์šฉ์ผ ๋ฟ์ž…๋‹ˆ๋‹ค.

์ธ์ฆ ์˜ˆ

/token ๋ฐ /me ๋‘ ๊ฐœ์˜ ๊ด€์‹ฌ ์—”๋“œํฌ์ธํŠธ๊ฐ€ ์žˆ๋Š” JWT ๋ณด์•ˆ API๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. /token ๋Š” ์ด๋ฉ”์ผ/๋น„๋ฐ€๋ฒˆํ˜ธ ์ž๊ฒฉ ์ฆ๋ช…์„ ์ˆ˜๋ฝํ•˜๊ณ  ์„œ๋ช…๋œ JWT( id_token )๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐ˜๋ฉด /me ๋Š” JWT ์ธ์ฆ ์‚ฌ์šฉ์ž์™€ ๊ด€๋ จ๋œ ํ”„๋กœํ•„ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. Auth0์˜ ์ž ๊ธˆ์—์„œ ๋‹ค์Œ AuthService.js ์„ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค(์ตœ์•…์˜ ์•„์ด๋””์–ด๋Š” ์•„๋‹ˆ์ง€๋งŒ ์ด๋ฒคํŠธ ์ด๋ฏธํ„ฐ ์ œ๊ฑฐ). ๊ฑฐ์˜ ๋ชจ๋“  JWT ํ† ํฐ ์ฒ˜๋ฆฌ๋ฅผ ์ถ”์ถœํ•˜๋ฏ€๋กœ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€์™€ ๊ณ ์ฐจ ๊ตฌ์„ฑ ์š”์†Œ(๋‚˜์ค‘์— ์ž์„ธํžˆ ์„ค๋ช…)์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// utils/AuthService.js
export default class AuthService {
  constructor(domain) {
    this.domain = domain || 'http://localhost:5000'
    this.fetch = this.fetch.bind(this)
    this.login = this.login.bind(this)
    this.getProfile = this.getProfile.bind(this)
  }

  login(email, password) {
    // Get a token
    return this.fetch(`${this.domain}/token`, {
      method: 'POST',
      body: JSON.stringify({
        email,
        password
      })
    }).then(res => {
      this.setToken(res.id_token)
      return this.fetch(`${this.domain}/user`, {
        method: 'GET'
      })
    }).then(res => {
      this.setProfile(res)
      return Promise.resolve(res)
    })
  }

  loggedIn(){
    // Checks if there is a saved token and it's still valid
    const token = this.getToken()
    return !!token && !isTokenExpired(token) // handwaiving here
  }

  setProfile(profile){
    // Saves profile data to localStorage
    localStorage.setItem('profile', JSON.stringify(profile))
  }

  getProfile(){
    // Retrieves the profile data from localStorage
    const profile = localStorage.getItem('profile')
    return profile ? JSON.parse(localStorage.profile) : {}
  }

  setToken(idToken){
    // Saves user token to localStorage
    localStorage.setItem('id_token', idToken)
  }

  getToken(){
    // Retrieves the user token from localStorage
    return localStorage.getItem('id_token')
  }

  logout(){
    // Clear user token and profile data from localStorage
    localStorage.removeItem('id_token');
    localStorage.removeItem('profile');
  }

  _checkStatus(response) {
    // raises an error in case response status is not a success
    if (response.status >= 200 && response.status < 300) {
      return response
    } else {
      var error = new Error(response.statusText)
      error.response = response
      throw error
    }
  }

  fetch(url, options){
    // performs api calls sending the required authentication headers
    const headers = {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    }

    if (this.loggedIn()){
      headers['Authorization'] = 'Bearer ' + this.getToken()
    }

    return fetch(url, {
      headers,
      ...options
    })
    .then(this._checkStatus)
    .then(response => response.json())
  }
}

๋‹ค์Œ์€ ํŽ˜์ด์ง€ ๋ณดํ˜ธ๋ฅผ ๋” ๊ฐ„๋‹จํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” HOC์ž…๋‹ˆ๋‹ค. ๋ฏผ๊ฐํ•œ ์ •๋ณด์˜ ์›์น˜ ์•Š๋Š” ํ”Œ๋ž˜์‹œ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ํŽ˜์ด์ง€๋Š” ์ฒซ ๋ฒˆ์งธ ๋ Œ๋”๋ง์—์„œ Loading... ๋ฅผ ์„œ๋ฒ„ ๋ Œ๋”๋งํ•˜๋Š” ๋™์•ˆ ๋ฐ˜์‘์ด ๋ถ€ํŒ…๋˜๊ณ  localStorage์—์„œ ํ† ํฐ์„ ์ฝ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋ณดํ˜ธ๋œ ํŽ˜์ด์ง€๊ฐ€ SEO๊ฐ€ ์•„๋‹˜์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํ˜„์žฌ๋กœ์„œ๋Š” ๊ดœ์ฐฎ์ง€๋งŒ ํ™•์‹คํžˆ ์ตœ์ ์€ ์•„๋‹™๋‹ˆ๋‹ค.

// utils/withAuth.js - a HOC for protected pages
import React, {Component} from 'react'
import AuthService from './auth'

export default function withAuth(AuthComponent) {
    const Auth = new AuthService('http://localhost:5000')
    return class Authenticated extends Component {
      constructor(props) {
        super(props)
        this.state = {
          isLoading: true
        };
      }

      componentDidMount () {
        if (!Auth.loggedIn()) {
          this.props.url.replaceTo('/')
        }
        this.setState({ isLoading: false })
      }

      render() {
        return (
          <div>
          {this.state.isLoading ? (
              <div>LOADING....</div>
            ) : (
              <AuthComponent {...this.props}  auth={Auth} />
            )}
          </div>
        )
      }
    }
}
// ./pages/dashboard.js
// example of a protected page
import React from 'react'
import withAuth from  '../utils/withAuth'

class Dashboard extends Component {
   render() {
     const user = this.props.auth.getProfile()
     return (   
         <div>Current user: {user.email}</div>
     )
   }
}

export default withAuth(Dashboard) 

๋กœ๊ทธ์ธ์ด ๊ณต๊ฐœ๋˜์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋Š” ํ˜„์žฌ HOC๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ AuthService์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ง์ ‘ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๊ฐ€์ž… ํŽ˜์ด์ง€์—์„œ๋„ ๋น„์Šทํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

// ./pages/login.js
import React, {Component} from 'react'
import AuthService from '../utils/AuthService'

const auth = new AuthService('http://localhost:5000')

class Login extends Component {
  constructor(props) {
    super(props)
    this.handleSubmit = this.handleSubmit.bind(this)
  }

  componentDidMount () {
    if (auth.loggedIn()) {
      this.props.url.replaceTo('/admin')   // redirect if you're already logged in
    }
  }

  handleSubmit (e) {
    e.preventDefault()
    // yay uncontrolled forms!
    auth.login(this.refs.email.value, this.refs.password.value)
      .then(res => {
        console.log(res)
        this.props.url.replaceTo('/admin')
      })
      .catch(e => console.log(e))  // you would show/hide error messages with component state here 
  }

  render () {
    return (
      <div>
         Login
          <form onSubmit={this.handleSubmit} >
            <input type="text" ref="email"/>
            <input type="password" ref="password"/>
            <input type="submit" value="Submit"/>
          </form>
      </div>
    )
  }
}

export default Login

Airbnb์˜ react-with-styles์—์„œ ์˜๊ฐ์„ ๋ฐ›์•„ ํŽ˜์ด์ง€์—์„œ ์‚ฌ์šฉํ•  HOC๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๊ฐ€ ๋  next-with-auth lib ์ž‘์—…๋„ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” ๋˜ํ•œ AuthService ์™€ ์ด HOC๋ฅผ ๋ณ‘ํ•ฉํ•˜์—ฌ ํ”Œ๋ ˆ์ดํ–ˆ์Šต๋‹ˆ๋‹ค. ํ•œ ๊ฐ€์ง€ ํ•ด๊ฒฐ์ฑ…์€ ์ด HOC๊ฐ€ redux connect์™€ ๊ฐ™์€ ๊ตฌ์„ฑ ์š”์†Œ ์™ธ์— ์ธ์ˆ˜๋กœ ๊ถŒํ•œ ์ˆ˜์ค€ ๊ธฐ๋Šฅ์„ ํ—ˆ์šฉํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์–ด์จŒ๋“  ๋‚ด ์ƒ๊ฐ์—๋Š” next-with-auth ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

// ./utils/withAuth.js
import nextAuth from 'next/auth'
import parseScopes from './parseScopes'

const Loading = () => <div>Loading...</div>

export default nextAuth({
  url: 'http://localhost:5000',
  tokenEndpoint: '/api/token',
  profileEndpoint: '/api/me',
  getTokenFromResponse: (res) => res.id_token,
  getProfileFromResponse: (res) => res,
  parseScopes,
})

Redux๋กœ ์ด ๋ชจ๋“  ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์€ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋ณต์žกํ•ด ๋ณด์ด์ง€๋งŒ ๊ธฐ๋ณธ์ ์œผ๋กœ wiki ์˜ˆ์ œ๋ฅผ ๋”ฐ๋ฅผ ์ˆ˜ ์žˆ์ง€๋งŒ AuthService๋ฅผ Actions(๋กœ๊ทธ์ธ ๋ฐ ๋กœ๊ทธ์•„์›ƒ)๋กœ ์ด๋™ํ•˜๊ณ  User Reducer๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„์— localStorage๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ํด๋ผ์ด์–ธํŠธ์—์„œ๋งŒ ์ด๋Ÿฌํ•œ ์ž‘์—…์„ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ž‘์—…์—์„œ ์ด๋ฅผ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฒฐ๊ตญ redux store๋Š” window ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ปจํ…์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹  window ์— ์‚ฌ์šฉ์ž๋ฅผ ์ง์ ‘ ์บ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. redux๋ฅผ ์›ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด react-broadcast ์‚ฌ์šฉํ•ด ๋ณผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ next/server ๊ฐ€ #25์— ๋”ฐ๋ผ ๋ฐฐ์†ก๋œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. next-with-auth ๋Š” ๋ฏธ๋“ค์›จ์–ด + HOC๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐœ๋ฐœ์ž๋กœ๋ถ€ํ„ฐ ๋ณต์žกํ•œ localStorage ๋Œ€ ์ฟ ํ‚ค ํ•ญ๋ชฉ์„ ์ถ”์ƒํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ† ํฐ ์ƒˆ๋กœ ๊ณ ์นจ๋„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ชจ๋“  208 ๋Œ“๊ธ€

์ œ์•ˆ: Redux ๋ฐ JWT๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์ œ ์ˆ˜ํ–‰

์ด์— ๋Œ€ํ•œ ์˜ˆ์ œ๋ฅผ ์ž‘์—… ์ค‘์ž…๋‹ˆ๋‹ค. ํ˜„์žฌ ์ƒ์œ„ ์ˆ˜์ค€ ๊ตฌ์„ฑ ์š”์†Œ์—์„œ componentWillReceiveProps๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๋ฐ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค(์‚ฌ์šฉ์ž๊ฐ€ ์ธ์ฆ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์ธ์ฆ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•  ๊ณ„ํš).

๊ทธ๋ž˜์„œ ๋‚˜๋Š” auth๊ฐ€ ์ˆ˜์˜์„ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๊ณณ์—์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด ํด๋ผ์ด์–ธํŠธ ์ธก ์ „์šฉ์ด๋ฉฐ ๊ถ๊ทน์ ์œผ๋กœ ์ „ํˆฌ์˜ ์ ˆ๋ฐ˜์— ๋ถˆ๊ณผํ•ฉ๋‹ˆ๋‹ค.

"๋งค์šฐ ์•ˆ์ „ํ•œ"

php์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Next์˜ ์›์ž ๋‹จ์œ„๋Š” ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค. ๊ฐ€์žฅ ๋ฉ‹์ง„ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜๋Š” ์š”์ฒญ๋œ ๊ฒฝ์šฐ์—๋งŒ ๊ฐ ํŽ˜์ด์ง€๋ฅผ ์ง€์—ฐ ๋กœ๋“œํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ์ธก ์ธ์ฆ๋งŒ ์žˆ์ง€๋งŒ ์„œ๋ฒ„ ๋ Œ๋”๋ง์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ณดํ˜ธ๋œ ํŽ˜์ด์ง€์˜ js๊ฐ€ ์‹ค์ œ๋กœ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋‹ค์šด๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค. ์•ž์œผ๋กœ Next๊ฐ€ ์„œ๋ฒ„ ์›Œํฌํ”Œ๋กœ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง ๋ฐ ๋ฆฌ๋””๋ ‰์…˜์„ ์ฐจ๋‹จํ•˜์—ฌ ์ด๋ฅผ ์™„์ „ํžˆ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—๋Š” ์ฟ ํ‚ค, ์„ธ์…˜ ๋ฐ AFAIK ์„ธ์…˜ ์ €์žฅ์†Œ๊ฐ€ ํ•„์š”ํ•˜์ง€๋งŒ ์ด๋Š” ์ด๋Ÿฌํ•œ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์•ฑ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐ ๋“œ๋Š” ๋น„์šฉ์ผ ๋ฟ์ž…๋‹ˆ๋‹ค.

์ธ์ฆ ์˜ˆ

/token ๋ฐ /me ๋‘ ๊ฐœ์˜ ๊ด€์‹ฌ ์—”๋“œํฌ์ธํŠธ๊ฐ€ ์žˆ๋Š” JWT ๋ณด์•ˆ API๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. /token ๋Š” ์ด๋ฉ”์ผ/๋น„๋ฐ€๋ฒˆํ˜ธ ์ž๊ฒฉ ์ฆ๋ช…์„ ์ˆ˜๋ฝํ•˜๊ณ  ์„œ๋ช…๋œ JWT( id_token )๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐ˜๋ฉด /me ๋Š” JWT ์ธ์ฆ ์‚ฌ์šฉ์ž์™€ ๊ด€๋ จ๋œ ํ”„๋กœํ•„ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. Auth0์˜ ์ž ๊ธˆ์—์„œ ๋‹ค์Œ AuthService.js ์„ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค(์ตœ์•…์˜ ์•„์ด๋””์–ด๋Š” ์•„๋‹ˆ์ง€๋งŒ ์ด๋ฒคํŠธ ์ด๋ฏธํ„ฐ ์ œ๊ฑฐ). ๊ฑฐ์˜ ๋ชจ๋“  JWT ํ† ํฐ ์ฒ˜๋ฆฌ๋ฅผ ์ถ”์ถœํ•˜๋ฏ€๋กœ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€์™€ ๊ณ ์ฐจ ๊ตฌ์„ฑ ์š”์†Œ(๋‚˜์ค‘์— ์ž์„ธํžˆ ์„ค๋ช…)์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// utils/AuthService.js
export default class AuthService {
  constructor(domain) {
    this.domain = domain || 'http://localhost:5000'
    this.fetch = this.fetch.bind(this)
    this.login = this.login.bind(this)
    this.getProfile = this.getProfile.bind(this)
  }

  login(email, password) {
    // Get a token
    return this.fetch(`${this.domain}/token`, {
      method: 'POST',
      body: JSON.stringify({
        email,
        password
      })
    }).then(res => {
      this.setToken(res.id_token)
      return this.fetch(`${this.domain}/user`, {
        method: 'GET'
      })
    }).then(res => {
      this.setProfile(res)
      return Promise.resolve(res)
    })
  }

  loggedIn(){
    // Checks if there is a saved token and it's still valid
    const token = this.getToken()
    return !!token && !isTokenExpired(token) // handwaiving here
  }

  setProfile(profile){
    // Saves profile data to localStorage
    localStorage.setItem('profile', JSON.stringify(profile))
  }

  getProfile(){
    // Retrieves the profile data from localStorage
    const profile = localStorage.getItem('profile')
    return profile ? JSON.parse(localStorage.profile) : {}
  }

  setToken(idToken){
    // Saves user token to localStorage
    localStorage.setItem('id_token', idToken)
  }

  getToken(){
    // Retrieves the user token from localStorage
    return localStorage.getItem('id_token')
  }

  logout(){
    // Clear user token and profile data from localStorage
    localStorage.removeItem('id_token');
    localStorage.removeItem('profile');
  }

  _checkStatus(response) {
    // raises an error in case response status is not a success
    if (response.status >= 200 && response.status < 300) {
      return response
    } else {
      var error = new Error(response.statusText)
      error.response = response
      throw error
    }
  }

  fetch(url, options){
    // performs api calls sending the required authentication headers
    const headers = {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    }

    if (this.loggedIn()){
      headers['Authorization'] = 'Bearer ' + this.getToken()
    }

    return fetch(url, {
      headers,
      ...options
    })
    .then(this._checkStatus)
    .then(response => response.json())
  }
}

๋‹ค์Œ์€ ํŽ˜์ด์ง€ ๋ณดํ˜ธ๋ฅผ ๋” ๊ฐ„๋‹จํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” HOC์ž…๋‹ˆ๋‹ค. ๋ฏผ๊ฐํ•œ ์ •๋ณด์˜ ์›์น˜ ์•Š๋Š” ํ”Œ๋ž˜์‹œ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ํŽ˜์ด์ง€๋Š” ์ฒซ ๋ฒˆ์งธ ๋ Œ๋”๋ง์—์„œ Loading... ๋ฅผ ์„œ๋ฒ„ ๋ Œ๋”๋งํ•˜๋Š” ๋™์•ˆ ๋ฐ˜์‘์ด ๋ถ€ํŒ…๋˜๊ณ  localStorage์—์„œ ํ† ํฐ์„ ์ฝ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋ณดํ˜ธ๋œ ํŽ˜์ด์ง€๊ฐ€ SEO๊ฐ€ ์•„๋‹˜์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํ˜„์žฌ๋กœ์„œ๋Š” ๊ดœ์ฐฎ์ง€๋งŒ ํ™•์‹คํžˆ ์ตœ์ ์€ ์•„๋‹™๋‹ˆ๋‹ค.

// utils/withAuth.js - a HOC for protected pages
import React, {Component} from 'react'
import AuthService from './auth'

export default function withAuth(AuthComponent) {
    const Auth = new AuthService('http://localhost:5000')
    return class Authenticated extends Component {
      constructor(props) {
        super(props)
        this.state = {
          isLoading: true
        };
      }

      componentDidMount () {
        if (!Auth.loggedIn()) {
          this.props.url.replaceTo('/')
        }
        this.setState({ isLoading: false })
      }

      render() {
        return (
          <div>
          {this.state.isLoading ? (
              <div>LOADING....</div>
            ) : (
              <AuthComponent {...this.props}  auth={Auth} />
            )}
          </div>
        )
      }
    }
}
// ./pages/dashboard.js
// example of a protected page
import React from 'react'
import withAuth from  '../utils/withAuth'

class Dashboard extends Component {
   render() {
     const user = this.props.auth.getProfile()
     return (   
         <div>Current user: {user.email}</div>
     )
   }
}

export default withAuth(Dashboard) 

๋กœ๊ทธ์ธ์ด ๊ณต๊ฐœ๋˜์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋Š” ํ˜„์žฌ HOC๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ AuthService์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ง์ ‘ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๊ฐ€์ž… ํŽ˜์ด์ง€์—์„œ๋„ ๋น„์Šทํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

// ./pages/login.js
import React, {Component} from 'react'
import AuthService from '../utils/AuthService'

const auth = new AuthService('http://localhost:5000')

class Login extends Component {
  constructor(props) {
    super(props)
    this.handleSubmit = this.handleSubmit.bind(this)
  }

  componentDidMount () {
    if (auth.loggedIn()) {
      this.props.url.replaceTo('/admin')   // redirect if you're already logged in
    }
  }

  handleSubmit (e) {
    e.preventDefault()
    // yay uncontrolled forms!
    auth.login(this.refs.email.value, this.refs.password.value)
      .then(res => {
        console.log(res)
        this.props.url.replaceTo('/admin')
      })
      .catch(e => console.log(e))  // you would show/hide error messages with component state here 
  }

  render () {
    return (
      <div>
         Login
          <form onSubmit={this.handleSubmit} >
            <input type="text" ref="email"/>
            <input type="password" ref="password"/>
            <input type="submit" value="Submit"/>
          </form>
      </div>
    )
  }
}

export default Login

Airbnb์˜ react-with-styles์—์„œ ์˜๊ฐ์„ ๋ฐ›์•„ ํŽ˜์ด์ง€์—์„œ ์‚ฌ์šฉํ•  HOC๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๊ฐ€ ๋  next-with-auth lib ์ž‘์—…๋„ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” ๋˜ํ•œ AuthService ์™€ ์ด HOC๋ฅผ ๋ณ‘ํ•ฉํ•˜์—ฌ ํ”Œ๋ ˆ์ดํ–ˆ์Šต๋‹ˆ๋‹ค. ํ•œ ๊ฐ€์ง€ ํ•ด๊ฒฐ์ฑ…์€ ์ด HOC๊ฐ€ redux connect์™€ ๊ฐ™์€ ๊ตฌ์„ฑ ์š”์†Œ ์™ธ์— ์ธ์ˆ˜๋กœ ๊ถŒํ•œ ์ˆ˜์ค€ ๊ธฐ๋Šฅ์„ ํ—ˆ์šฉํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์–ด์จŒ๋“  ๋‚ด ์ƒ๊ฐ์—๋Š” next-with-auth ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

// ./utils/withAuth.js
import nextAuth from 'next/auth'
import parseScopes from './parseScopes'

const Loading = () => <div>Loading...</div>

export default nextAuth({
  url: 'http://localhost:5000',
  tokenEndpoint: '/api/token',
  profileEndpoint: '/api/me',
  getTokenFromResponse: (res) => res.id_token,
  getProfileFromResponse: (res) => res,
  parseScopes,
})

Redux๋กœ ์ด ๋ชจ๋“  ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์€ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋ณต์žกํ•ด ๋ณด์ด์ง€๋งŒ ๊ธฐ๋ณธ์ ์œผ๋กœ wiki ์˜ˆ์ œ๋ฅผ ๋”ฐ๋ฅผ ์ˆ˜ ์žˆ์ง€๋งŒ AuthService๋ฅผ Actions(๋กœ๊ทธ์ธ ๋ฐ ๋กœ๊ทธ์•„์›ƒ)๋กœ ์ด๋™ํ•˜๊ณ  User Reducer๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„์— localStorage๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ํด๋ผ์ด์–ธํŠธ์—์„œ๋งŒ ์ด๋Ÿฌํ•œ ์ž‘์—…์„ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ž‘์—…์—์„œ ์ด๋ฅผ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฒฐ๊ตญ redux store๋Š” window ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ปจํ…์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹  window ์— ์‚ฌ์šฉ์ž๋ฅผ ์ง์ ‘ ์บ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. redux๋ฅผ ์›ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด react-broadcast ์‚ฌ์šฉํ•ด ๋ณผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ next/server ๊ฐ€ #25์— ๋”ฐ๋ผ ๋ฐฐ์†ก๋œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. next-with-auth ๋Š” ๋ฏธ๋“ค์›จ์–ด + HOC๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐœ๋ฐœ์ž๋กœ๋ถ€ํ„ฐ ๋ณต์žกํ•œ localStorage ๋Œ€ ์ฟ ํ‚ค ํ•ญ๋ชฉ์„ ์ถ”์ƒํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ† ํฐ ์ƒˆ๋กœ ๊ณ ์นจ๋„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์„ ์‹œ๋„ํ•˜๊ฒŒ๋˜์–ด ๊ธฐ์ฉ๋‹ˆ๋‹ค! ๋ฒ ์–ด๋ณธ ๊ตฌํ˜„์— ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค :)

@jaredpalmer ๋น„์Šทํ•œ ์ž‘์—…์„ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ์„œ๋ฒ„ ์ธก์—์„œ ๋ Œ๋”๋ง๋  ๋•Œ AuthService ๋Š” ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๊นŒ? ์„œ๋ฒ„๋Š” JWT์— ์•ก์„ธ์Šคํ•ด์•ผ ํ•˜์ง€๋งŒ ๋กœ์ปฌ ์ €์žฅ์†Œ์—์„œ ์ฝ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

@amccloud ๊ทธ๋ ‡์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์ด ์ „์ฒด ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค. HOC๋Š” ๋ณดํ˜ธ๋œ ๊ฒฝ๋กœ์—์„œ <div>Loading..</div> ๋ฅผ ๋ Œ๋”๋งํ•˜๊ณ  ํ† ํฐ์„ ์ฝ๊ณ  componentDidMount ์—์„œ ๋ฆฌ๋””๋ ‰์…˜ํ• ์ง€ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์›ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•˜๊ณ  ์„œ๋ฒ„ ์ธก์—์„œ ๋ Œ๋”๋งํ•˜๋ ค๋ฉด Next์— #25๊ฐ€ ํ•„์š”ํ•˜๊ฑฐ๋‚˜ ์ตœ์†Œํ•œ JWT AFAIK ๊ฐ’์œผ๋กœ ์ฟ ํ‚ค๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๋‚˜๋Š” cookie-js๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฟ ํ‚ค๋ฅผ ์„ค์ •ํ–ˆ์ง€๋งŒ ์•ฝ๊ฐ„์˜ ํ•ดํ‚น์ž…๋‹ˆ๋‹ค.
๋ฌธ์ œ๋Š” ์ฟ ํ‚ค๋ฅผ ๋ณด๋‚ด์ง€ ์•Š์œผ๋ฉด ์ธ์ฆ๋œ ๊ฒฝ๋กœ์—์„œ nextjs ๋ฐ ์„œ๋ฒ„ ์ธก ๋ Œ๋”๋ง์˜ ๋ชจ๋“  ์ด์ ์„ ์žƒ๊ฒŒ ๋œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

@jaredpalmer ๊ต‰์žฅํ•ฉ๋‹ˆ๋‹ค! ๋…ธ๋ ฅ์— ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ๋‹ค์Œ ๋‚ ์— ๊ท€ํ•˜์˜ ์˜ˆ์ œ ๊ตฌํ˜„์„ ์™„๋ฃŒํ•˜๋ ค๊ณ  ๋…ธ๋ ฅํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค(๋˜๋Š” ์›ํ•˜๋Š” ๊ฒฝ์šฐ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋จ).

์—์•ผ๋””์•ผ! https://github.com/luisrudge/next.js-auth0์— nextjs ๋ฐ auth0์ด ํฌํ•จ๋œ ์˜ˆ์ œ๋ฅผ ๊ฒŒ์‹œํ–ˆ์Šต๋‹ˆ๋‹ค.
๊ธฐ๋ณธ ๋ ˆ์ด์•„์›ƒ์˜ ๊ฐœ๋…๊ณผ ์‚ฌ์šฉ์ž๊ฐ€ ์ธ์ฆ๋  ๋•Œ๋งŒ ๋กœ๋“œ๋˜๋Š” "๋ณด์•ˆ ํŽ˜์ด์ง€"๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
๋‹น์‹ ์˜ ์ƒ๊ฐ์„ ์•Œ๋ ค์ฃผ์„ธ์š”๐ŸŽ‰

@luisrudge ๊ต‰์žฅํ•ฉ๋‹ˆ๋‹ค. ๋ณต์ œํ•˜๊ณ  ์ผ๋ถ€ ๋ณ€๊ฒฝ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์ง€๋งŒ ๋ฉ‹์ง€๊ฒŒ ๋ณด์ž…๋‹ˆ๋‹ค.

๋ฉ‹์žˆ๋Š”! ๋ฌด์—‡์ด ๋ถ€์กฑํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๊นŒ? ์–ด๋–ค ๋ณ€ํ™”๋ฅผ ์ƒ๊ฐํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ?

2016๋…„ 11์›” 6์ผ ์ผ์š”์ผ ์˜คํ›„ 1์‹œ 12๋ถ„ -0200์— "Dan Zajdband" < [email protected] [email protected] >์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ผ์Šต๋‹ˆ๋‹ค.

@luisr udgehttps://github.com/luisrudge ๊ต‰์žฅํ•ฉ๋‹ˆ๋‹ค. ๋ณต์ œํ•˜๊ณ  ์ผ๋ถ€ ๋ณ€๊ฒฝ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์ง€๋งŒ ๋ฉ‹์ง€๊ฒŒ ๋ณด์ž…๋‹ˆ๋‹ค.

๋‹น์‹ ์ด ์–ธ๊ธ‰๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์„ ๋ฐ›๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์ด ์ด๋ฉ”์ผ์— ์ง์ ‘ ๋‹ต์žฅํ•˜๊ฑฐ๋‚˜ Gi tHubhttps://github.com/zeit/next.js/issues/153#issuecomment -258687108์—์„œ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ์ฝ๊ธฐ๋ฅผ ์Œ์†Œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค https://github.com/notifications/unsubscribe-auth/ AA5cE8NIsvQ_ITjc1gArtTFgNXzEda4TSks5q7e5NgaJpZM4KkJmi.

1) Linting์— standard ์‚ฌ์šฉ(๋”ฐ๋ผ์„œ ๋‹ค์Œ์— ๋นŒ๋“œํ•  ๋ชจ๋“  ๊ฒƒ๊ณผ ์ผ์น˜ํ•จ)
2) @rauchg๊ฐ€ ์š”์ฒญํ•œ ๋ฉ€ํ‹ฐํƒญ ์ง€์› ์ถ”๊ฐ€
3) CSS ๋ถ€๋ถ„์„ ๋‹จ์ˆœํ™” ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ™๋ณด๊ธ€ ๋ณด๋‚ด๋“œ๋ฆฝ๋‹ˆ๋‹ค :)

๋ฉ€ํ‹ฐ ํƒญ ์ง€์›์ด๋ž€ ๋ฌด์—‡์„ ์˜๋ฏธํ•ฉ๋‹ˆ๊นŒ?

2016๋…„ 11์›” 6์ผ ์ผ์š”์ผ ์˜คํ›„ 1:16 -0200์— "Dan Zajdband" < [email protected] [email protected] >์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ผ์Šต๋‹ˆ๋‹ค.

1) ๋ฆฐํŠธ์— ๋Œ€ํ•œ ํ‘œ์ค€ ์‚ฌ์šฉ(๋”ฐ๋ผ์„œ ์šฐ๋ฆฌ๊ฐ€ ๋‹ค์Œ์— ๊ตฌ์ถ•ํ•  ๋ชจ๋“  ๊ฒƒ๊ณผ ์ผ์น˜ํ•จ)
2) @rauchghttps ://github.com/rauchg์—์„œ ์š”์ฒญํ•œ
3) CSS ๋ถ€๋ถ„์„ ๋‹จ์ˆœํ™” ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ™๋ณด๊ธ€ ๋ณด๋‚ด๋“œ๋ฆฝ๋‹ˆ๋‹ค :)

๋‹น์‹ ์ด ์–ธ๊ธ‰๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์„ ๋ฐ›๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์ด ์ด๋ฉ”์ผ์— ์ง์ ‘ ๋‹ต์žฅํ•˜๊ฑฐ๋‚˜ Gi tHubhttps://github.com/zeit/next.js/issues/153#issuecomment -258687373์—์„œ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ํ•˜์„ธ์š”. AA5cE1A6jq4KZc9_ynukTCI4mU-rdsNaks5q7e81gaJpZM4KkJmi.

2๊ฐœ์˜ ์—ด๋ฆฐ ํƒญ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜๋‚˜๋Š” ๋กœ๊ทธ์•„์›ƒํ•˜๊ณ  ๋‹ค๋ฅธ ํ•˜๋‚˜๋Š” ์ž๋™์œผ๋กœ ๋กœ๊ทธ์•„์›ƒ๋ฉ๋‹ˆ๋‹ค.

์•„. ์ •๋ง ๋ฉ‹์ง€๋„ค์š”!

2016๋…„ 11์›” 6์ผ ์ผ์š”์ผ ์˜คํ›„ 1์‹œ 21๋ถ„ -0200์— "Dan Zajdband" < [email protected] [email protected] >์ด(๊ฐ€) ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ผ์Šต๋‹ˆ๋‹ค.

2๊ฐœ์˜ ์—ด๋ฆฐ ํƒญ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜๋‚˜๋Š” ๋กœ๊ทธ์•„์›ƒํ•˜๊ณ  ๋‹ค๋ฅธ ํƒญ์—์„œ๋Š” ์ž๋™์œผ๋กœ ๋กœ๊ทธ์•„์›ƒ๋ฉ๋‹ˆ๋‹ค.

๋‹น์‹ ์ด ์–ธ๊ธ‰๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์„ ๋ฐ›๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์ด ์ด๋ฉ”์ผ์— ์ง์ ‘ ๋‹ต์žฅํ•˜๊ฑฐ๋‚˜ Gi tHubhttps://github.com/zeit/next.js/issues/153#issuecomment -258687707์—์„œ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ํ•˜์„ธ์š”. AA5cE9e2DA4_GgNQIVTMp0hx74G-6RmUks5q7fBfgaJpZM4KkJmi.

์•ˆ๋…•ํ•˜์„ธ์š” @luisrudge https://github.com/luisrudge/next.js-auth0/pull/2 ๋ณ€๊ฒฝ ์‚ฌํ•ญ๊ณผ ํ•จ๊ป˜ PR์„ ๋ณด๋ƒˆ์Šต๋‹ˆ๋‹ค.

ํ•ด์ฃผ์…”์„œ ์ •๋ง ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค <3

btw ์ด๊ฒƒ์€ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค.

2016-11-06 11 14 31

@impronunciable @luisrudge ํ™˜์ƒ์ ์ธ ๊ตฌํ˜„! Auth0 ์—†์ด ์‚ฌ์šฉํ•˜๋ ค๋ฉด ./utils ๋””๋ ‰ํ† ๋ฆฌ์˜ ํŒŒ์ผ๋งŒ ๋ณ€๊ฒฝํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์•„๋งˆ๋„ lock.js ์žˆ์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” ์ด๊ฒƒ์„ ๊ณง ์‹œ๋„ํ•  ๊ฒƒ์ด๋‹ค. ๋ฉ€ํ‹ฐํƒญ์ด ๋ฉ‹์ง€๋„ค์š” ๐Ÿ’ฏ

@ugiacoman ๋‚˜๋Š” passwordless.net์œผ๋กœ ์ž‘์€ ์„œ๋ฒ„๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. ์‹œ์ž‘์ ์œผ๋กœ ๋‚ด ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์˜ค๋ ค๋ฉด ์•Œ๋ ค์ฃผ์„ธ์š”.

@impronuncible ๊ต‰์žฅ

@impronuncible ๋‚˜๋Š” ์•”ํ˜ธ less.net์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  Passport-local์„ ์‚ฌ์šฉํ•˜๊ณ  ์‚ฌ์šฉ์ž์—๊ฒŒ ์ด๋ฉ”์ผ๊ณผ ์ฟผ๋ฆฌ ๋ฌธ์ž์—ด์˜ ํ† ํฐ์ด ํฌํ•จ๋œ ๋งํฌ๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@impronuncible โค๏ธ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

@ugiacoman ์˜ˆ, auth0 ์ข…์†์„ฑ์„ ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ๋ณ„๋„์˜ API๋ฅผ ๊ฐ–๊ณ  ์‹ถ์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

@jaredpalmer ๋‚ด๊ฐ€ ์•„๋Š” ํ•œ, #25๊ฐ€ ์žˆ์œผ๋ฉด getInitialProps ์—์„œ ์„œ๋ฒ„ ์ธก req ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ cookie-parser ๋ฅผ ์ ์šฉํ•˜๋Š” ๋ฐ ๋ฐฉํ•ด๊ฐ€ ๋˜๋Š” ๊ฒƒ์€ ์—†๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์ธก ์ธ์ฆ ๋ฐ ์„ธ์…˜ ๊ด€๋ฆฌ๋Š” ์ €์—๊ฒŒ ์™„์ „ํžˆ ์ƒˆ๋กœ์šด ๊ฒƒ์ž…๋‹ˆ๋‹ค ๐Ÿ˜ฌ

localStorage ๊ณ ๋ คํ•œ BTW๋Š” ์„œ๋ฒ„ ์ธก์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ฟ ํ‚ค๊ฐ€ ์„œ๋ฒ„ ์ธก ์„ธ์…˜์„ ๊ฐ–๋Š” ์œ ์ผํ•œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๊นŒ? ๋ง‰์—ฐํ•œ ๊ธฐ์–ต์ด ์žˆ๋Š”๋ฐ ๊ทธ๊ฒŒ ๊ฐ€์žฅ ์•ˆ์ „ํ•˜์ง€ ์•Š์„๊นŒ? ๊ทธ๋Ÿฌ๋‚˜ ๋‹ค๋ฅธ ์˜ต์…˜์ด ์žˆ์Šต๋‹ˆ๊นŒ?

@sedubois

์ฟ ํ‚ค ์ ‘๊ทผ ๋ฐฉ์‹์€ ์ œ๋Œ€๋กœ ์ˆ˜ํ–‰๋˜๋ฉด ๋งค์šฐ ์•ˆ์ „ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.

  • httpOnly ํ”Œ๋ž˜๊ทธ ์‚ฌ์šฉ(์ฟ ํ‚ค์— ๋Œ€ํ•œ JavaScript ์•ก์„ธ์Šค ๋ฐฉ์ง€)
  • ๋ณด์•ˆ ํ”Œ๋ž˜๊ทธ ์‚ฌ์šฉ(https ์š”์ฒญ์— ๋Œ€ํ•ด์„œ๋งŒ ์ฟ ํ‚ค ์„ค์ •)
  • ์„œ๋ช…๋œ ์ฟ ํ‚ค(์ฟ ํ‚ค ์ถœ์ฒ˜ ํ™•์ธ)

๋˜ํ•œ ์„œ๋ฒ„์—์„œ ์ง์ ‘ ์ธ์ฆ ์ •๋ณด์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ ๋Œ€๊ธฐ ์‹œ๊ฐ„์˜ ์ด์ ์ด ๋งค์šฐ ๋†’์Šต๋‹ˆ๋‹ค.

์ด ์˜ˆ์ œ๋ฅผ examples/ ๋กœ ์˜ฎ๊ฒจ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉ์ž ์ง€์ • ์ต์Šคํ”„๋ ˆ์Šค ์„œ๋ฒ„์—์„œ nextjs๋ฅผ ๋ž˜ํ•‘ํ•˜์—ฌ ๋™ํ˜• ์ฟ ํ‚ค์— react-cookie ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

const express = require('express')
const next = require('next')
const cookie = require('react-cookie')
const cookieParser = require('cookie-parser')

const app = next({ dev: true, dir: process.cwd() })
const handle = app.getRequestHandler()

app.prepare().then(() => {
  const server = express()
  server.use(cookieParser())       // <---- this line

  server.get('*', (req, res) => {
    cookie.plugToRequest(req, res) // <---- this line
    return handle(req, res)
  })

  server.listen(3000, (err) => {
    if (err) throw err
    console.log('> Ready on http://localhost:3000')
  })
})

์ด๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„ ์ธก์—์„œ ์ธ์ฆ๋œ ์š”์ฒญ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์›๋ž˜ ๊ธ€๋จธ๋ฆฌ ๊ธฐํ˜ธ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์ง€ ์•Š์ง€๋งŒ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๊ฐ„์˜ ์ƒํƒœ ๊ณต์œ  ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด ๋ฌผ๊ฑด์„ ๋งŽ์ด ๋ฐฐ์šฐ๋Š” ์‚ฌ๋žŒ์˜ ๊ด€์ ์—์„œ. ์˜ˆ์ œ๊ฐ€ auth0๊ณผ ๊ฐ™์€ ํƒ€์‚ฌ ์„œ๋น„์Šค์— ์˜์กดํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ข‹์„ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์‹ ๊ทœ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ/๊ฐ€์ž… ์–‘์‹๊ณผ Redux ๋ฐ JWT๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋” ๊ธฐ๋ณธ์ ์ธ ์˜ˆ๋ฅผ ๋ณด๋Š” ๊ฒƒ์ด ๋” ์œ ๋ฆฌํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

_bundle_ ๊ณ„ํšํ•œ ์˜ˆ์ œ๋Š” ์˜คํ”ˆ ์†Œ์Šค Node.js ์„œ๋ฒ„ API๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค.

https://github.com/iaincollins/nextjs-starter ์˜ ์˜ˆ์ œ ์Šคํƒ€ํ„ฐ ํ”„๋กœ์ ํŠธ์— ์ด๋ฉ”์ผ ๊ธฐ๋ฐ˜ ์ธ์ฆ ์˜ˆ์ œ๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

์„ธ์…˜ ์ง€์›(๋ฐฑ์—”๋“œ์˜ Express Sessions ๋ฐ ํ”„๋ŸฐํŠธ ์—”๋“œ ์บ์‹œ๋ฅผ ์œ„ํ•œ ๋ธŒ๋ผ์šฐ์ € sessionStorage API ํฌํ•จ), httpOnly ์ฟ ํ‚ค, CSRF ํ”„๋กœ์ ์…˜, ๋‚ด์žฅ SMTP๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฉ”์ผ ๋ณด๋‚ด๊ธฐ, ๊ธฐ๋ณธ์ ์œผ๋กœ SQL Lite๋กœ ๋ฐฑ์—”๋“œ ๋ณ€๊ฒฝ ์šฉ์ด . ์‹คํ–‰์„ ์œ„ํ•ด ๊ตฌ์„ฑ์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด ํ”„๋กœ์ ํŠธ์—๋Š” ๋ ˆ์ด์•„์›ƒ ํŽ˜์ด์ง€, ์‚ฌ์šฉ์ž ์ง€์ • ๊ฒฝ๋กœ๋„ ์žˆ์œผ๋ฉฐ Wiki์˜ ์‹œ๊ณ„ ์˜ˆ์ œ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ธ์ฆ์˜ ๋ฉ‹์ง„ ์˜ˆ๋Š” ์•„๋‹ˆ์ง€๋งŒ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๊ณ  ๊ฐ€์ง€๊ณ  ๋†€๊ธฐ ์‰ฌ์šด ๊ฐ„๋‹จํ•œ ํ”„๋กœ์ ํŠธ๋กœ ์‰ฝ๊ฒŒ ์‹œ์ž‘ํ•˜๋ ค๋Š” ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

JWT๋ฅผ ์‚ฌ์šฉํ•œ ์˜ˆ๊ฐ€ ์ข‹์€ ์ƒ๊ฐ์ธ ๊ฒƒ ๊ฐ™๋‹ค๋Š” @iamjacks์˜ ๋ง์— ๋™์˜ํ•ฉ๋‹ˆ๋‹ค.

์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๋ฅผ ๊ฐœ์„ ํ•˜๊ณ  ์‚ฌ์šฉ์ž๊ฐ€ ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ„๋‹จํ•œ ํ”„๋กœํ•„ ํŽ˜์ด์ง€์™€ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ฒŒ ๋˜์–ด ๊ธฐ์ฉ๋‹ˆ๋‹ค. ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ์œ ์šฉํ•  ๊ฒฝ์šฐ Facebook, Google ๋ฐ Twitter์šฉ oAuth ์˜ˆ์ œ์™€ ์—ฌ๊ถŒ ํ†ตํ•ฉ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ๋žŒ๋“ค์ด ๊ตฌ์„ฑ ์š”์†Œ์— ์„ธ์…˜ ์ •๋ณด๋ฅผ ์ „ํŒŒ/๋…ธ์ถœํ•˜๋Š” ๋” ๋‚˜์€ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์ข‹์€ ์•„์ด๋””์–ด๊ฐ€ ์žˆ๋‹ค๋ฉด ์ €๋Š” ๋งค์šฐ ๊ด€์‹ฌ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

Example screenshot showing what to expect

์ •๋ง ๋†€๋ผ์šด @iaincollins์ž…๋‹ˆ๋‹ค. 2.0 ๋ฆด๋ฆฌ์Šค ๋…ธํŠธ์—์„œ ์ด ๊ธฐ๋Šฅ์„ ํ™•์‹คํžˆ ์‚ฌ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. :)

@rauchg ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค! :)

์ด ์˜ˆ์ œ์—์„œ ์„ธ์…˜์€ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ๋‹ค๋Š” ์ ์„ ๋ช…์‹œ์ ์œผ๋กœ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, JavaScript๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ (sessionStorage๊ฐ€ ์—†๋Š” ์‹œ์Šคํ…œ์—์„œ) ๋™์ผํ•œ ์„ธ์…˜์ด ๋‘˜ ๋‹ค์—์„œ ๊ณต์œ ๋ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ๋‹ฌ์„ฑํ•˜๋ฉด Session ๊ตฌ์„ฑ ์š”์†Œ์—์„œ ์•ฝ๊ฐ„ ๋ณต์žกํ•ด์ง‘๋‹ˆ๋‹ค. ์ด ๊ตฌ์„ฑ ์š”์†Œ๋Š” req.connection._httpMessage.locals._csrf์™€ ๊ฐ™์€ ์ด๋ฆ„์„ ๊ฐ€์ง„ ๋ณ€์ˆ˜๋ฅผ ์กฐ์‚ฌํ•˜์—ฌ ์„œ๋ฒ„ ํ—ค๋”์—์„œ CSRF ํ† ํฐ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. req.locals._csrf๋ฅผ ํ†ตํ•ด ์ผ๋ฐ˜์ ์œผ๋กœ ์•ก์„ธ์Šคํ•˜๋ฏ€๋กœ Express์— ๋…ธ์ถœ๋œ req ๊ฐœ์ฒด์™€ ์•ฝ๊ฐ„ ๋‹ค๋ฆ…๋‹ˆ๋‹ค(๊ทธ๋Ÿฌ๋‚˜ req.session์€ ๋‘˜ ๋‹ค ๋™์ผํ•จ).

localStorage๋Š” ์•ˆ์ „ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•˜๋Š” ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ? ๋ˆ„๊ตฌ๋‚˜ localStorage ๋ฐ์ดํ„ฐ๋ฅผ ํ›”์ณ์„œ ์ž์‹ ์˜ ๋ธŒ๋ผ์šฐ์ €์— ๋‹ค์‹œ ๋„ฃ์„ ์ˆ˜ ์žˆ์œผ๋ฉฐ ํฌ์ƒ์ž ์‚ฌ์šฉ์ž๋กœ ๊ธฐ๋ก๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!!

@Chathula ์‚ฌ์‹ค์ด ์•„๋‹™๋‹ˆ๋‹ค. ์ด ์ฃผ์žฅ์€ ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
๋ˆ„๊ตฌ๋“ ์ง€ ๋ธŒ๋ผ์šฐ์ €์— ๋ฌผ๋ฆฌ์ ์œผ๋กœ ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ๋ฌด์—‡์ด๋“  ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฟ ํ‚ค์˜ ๊ฒฝ์šฐ์—๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค.

์ธ์ฆ์„ ์œ„ํ•ด localStorage๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ์ฟ ํ‚ค ๊ธฐ๋ฐ˜ ์ดˆ ๋ฌธ์ œ๋ฅผ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ ํ•œํŽธ์œผ๋กœ๋Š” SSR์— ์˜ํ–ฅ์„ ๋ฏธ์นฉ๋‹ˆ๋‹ค.

@arunoda ์ €๋Š” Laravel API์™€ Next.js ํด๋ผ์ด์–ธํŠธ๋กœ ๋กœ๊ทธ์ธ์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” localStorage ์•ˆ์— authUser access_token์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ์ธ์ฆ์„ ํ†ตํ•ด ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์•ˆ์ „ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ localStorage ๋ฐ์ดํ„ฐ๋ฅผ ํ›”์นœ ๊ฒฝ์šฐ. ๊ทธ/๊ทธ๋…€๋Š” ๊ทธ๊ฒƒ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ˆ„๊ตฐ๊ฐ€๊ฐ€ localStorage ๋ฐ์ดํ„ฐ๋ฅผ ํ›”์นœ ๊ฒฝ์šฐ.

์–ด๋–ป๊ฒŒ? ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฌผ๋ฆฌ์ ์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ๊ทธ ์‚ฌ๋žŒ์€ ๋ฌด์—‡์ด๋“  ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ ์šฐ๋ฆฌ๋Š” ๊ทธ๊ฒƒ์— ๋Œ€ํ•ด ๊ฑฑ์ •ํ•ด์„œ๋Š” ์•ˆ๋ฉ๋‹ˆ๋‹ค.

์•”ํ˜ธํ™”๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ›จ์”ฌ ๋” ์•ˆ์ „ํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜๋Š” ์—†๋‚˜์š”?

@Chathula ์ด๊ฒƒ์€ ์ฃผ์ œ์—์„œ ๋ฒ—์–ด๋‚ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ Next.js์™€ ์ •ํ™•ํžˆ ๊ด€๋ จ์ด ์—†์œผ๋ฉฐ ์šฐ๋ฆฌ๋Š” ์›น์ด ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋Š” ๋ฐฉ์‹๊ณผ ํ•จ๊ป˜ ๊ฐ€๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

@Chathula ๋Š” ์œ„์˜ ์Šคํƒ€ํ„ฐ ํ”„๋กœ์ ํŠธ์—์„œ ์ƒˆ ์Šค๋ ˆ๋“œ๋ฅผ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@ ์•„๋ฃจ

@Chathula ํŠน์ • ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์Šคํƒ€ํ„ฐ ํ”„๋กœ์ ํŠธ ๋ฌธ์ œ์—์„œ ๋” ์ž์„ธํžˆ ๋…ผ์˜ํ•˜๊ฒŒ ๋˜์–ด ๊ธฐ์ฉ๋‹ˆ๋‹ค.

์‚ฌ๋žŒ๋“ค์ด ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋†€๋ผ์ง€ ์•Š๋„๋ก ์˜คํ•ด๋ฅผ ์ˆ˜์ •ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

Web Storage API(์˜ˆ: localStorage ๋ฐ sessionStorage)๋Š” httpOnly๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์€ ์ฟ ํ‚ค์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋™์ผํ•œ ์ถœ์ฒ˜ ์ •์ฑ…(ํ”„๋กœํ† ์ฝœ, ํ˜ธ์ŠคํŠธ ์ด๋ฆ„, ํฌํŠธ ๋ฒˆํ˜ธ)์„ ํ†ตํ•ด ์ œํ•œ๋˜๋ฉฐ "๋ˆ„๊ตฌ๋‚˜ [๊ทธ๊ฒƒ]์„ ํ›”์น  ์ˆ˜ ์žˆ๋‹ค"๋Š” ๊ฒƒ์€ ์‚ฌ์‹ค์ด ์•„๋‹™๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์˜ˆ, ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์˜ ๊ต์ฐจ ์‚ฌ์ดํŠธ ์Šคํฌ๋ฆฝํŒ… ์ทจ์•ฝ์ ์„ ํ†ตํ•ด ์‚ฌ์ดํŠธ์—์„œ ์ž„์˜์˜ JavaScript๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฉด ํ•ด๋‹น ์ €์žฅ์†Œ์—๋„ ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์„ธ์…˜ ์‹๋ณ„์ž๋ฅผ ์ €์žฅํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค.

์ด๊ฒƒ์ด ์„ธ์…˜ ํ† ํฐ ์ž์ฒด๊ฐ€ localStorage/sessionStorage์— ์ €์žฅ๋˜์ง€ ์•Š๊ณ  JavaScript์—์„œ ์ฝ์„ ์ˆ˜ ์—†์œผ๋ฉฐ HTTP ์ „์šฉ ์ฟ ํ‚ค๋ฅผ ํ†ตํ•ด์„œ๋งŒ ์ „์†ก๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋Š” ์ด์œ ์ž…๋‹ˆ๋‹ค(์„ธ์…˜ ํด๋ž˜์Šค๊ฐ€ fetch() ๋Œ€์‹  XMLHttpRequest()๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ์ž…๋‹ˆ๋‹ค. ํด๋ž˜์Šค ๋ฌธ์„œ์— ์„ค๋ช…๋œ ๋Œ€๋กœ).

์ฆ‰, ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ Cross Site Scripting Vulnerability(๊ต์ฐจ ์‚ฌ์ดํŠธ ์Šคํฌ๋ฆฝํŒ…) ์ทจ์•ฝ์ ์„ ์•…์šฉํ•˜๊ณ  ์›น ์•ฑ์—์„œ ์ž„์˜์˜ JavaScript๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋”๋ผ๋„ ์—ฌ์ „ํžˆ ์‚ฌ์šฉ์ž ์„ธ์…˜ ํ† ํฐ์„ ์ฝ๊ฑฐ๋‚˜ ๋‚ด๋ณด๋‚ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ ์•„๋งˆ๋„ ๋ฌธ์„œ์—์„œ ๋…ธ๋ ฅํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ๋Š” ์ค‘์š”ํ•œ ๊ตฌ๋ถ„์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ฐธ๊ณ : ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ์˜ ์ถ”๊ฐ€ ์•”ํ˜ธํ™”๋Š” ์—ฌ๊ธฐ์—์„œ ๋„์›€์ด ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์•ฑ์€ ํ•ญ์ƒ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ์–ด์•ผ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค(๋”ฐ๋ผ์„œ ์•ฑ์— ์„ค๋ช… ํ‚ค๋ฅผ ์ €์žฅํ•ด์•ผ ์•”ํ˜ธํ™”๋ฅผ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋Š” ์ƒ๋‹นํžˆ ๋ฌด์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค).

_์—…๋ฐ์ดํŠธ: ์ง€๋‚œ ์ฃผ ๋˜๋Š” 2์ฃผ ๋™์•ˆ sessionStorage๊ฐ€ ํƒญ ๊ฐ„์— ๊ณต์œ ๋˜์ง€ ์•Š๊ณ  ๋ฏผ๊ฐํ•˜์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด ์˜ˆ์ œ๋Š” sessionStorage๋ณด๋‹ค localStorage๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ๋ฆฌํŒฉํ† ๋ง๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ถˆํ•„์š”ํ•œ ์ธ์ฆ ํ™•์ธ ํšŸ์ˆ˜๋ฅผ ์ค„์ด๊ณ  ํƒญ ๊ฐ„์— ์„ธ์…˜ ์ƒํƒœ๋ฅผ ์ผ๊ด€๋˜๊ฒŒ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค._

์‹คํ—˜ํ•˜๋Š” ๋™์•ˆ ์ด ์ƒ˜ํ”Œ ์•ฑ์„ ๋งŒ๋“  ์ผ๋ถ€ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

https://github.com/possibilities/next.js-with-auth

์ด ์žฅ๋‚œ๊ฐ ๋ฐฑ์—”๋“œ ์ง€์›:

https://github.com/possibilities/micro-auth

์—ฌ๊ธฐ์— ๋ฐฐํฌ:

https://next-with-auth.now.sh/

๋ฐฑ์—”๋“œ:

https://micro-auth.now.sh/

@possibilities ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค Mike! ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋‚ด๊ฐ€ ์ƒ๊ฐํ•˜๊ธฐ์— ์ข‹์€ ์‹ค์šฉ์ฃผ์˜๊ฐ€ ๋  ์ˆ˜ ์žˆ๊ณ  ์˜๊ฐ์„ ์–ป์„ ์ˆ˜ ์žˆ๋Š” ๋ณด์•ˆ ํŽ˜์ด์ง€๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ฃผ๋Š” ๋ณ„๋„์˜ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋ฅผ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค. next.js-with-auth ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์—์„œ ์ œ๊ธฐํ•  ๋ช‡ ๊ฐ€์ง€ ์•„์ด๋””์–ด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์ข€ ๋” ์ƒ๊ฐํ•ด ๋ณธ ํ›„์—๋Š” ์œ„์˜ ๋…ธ๋ ฅ์„ ์ข‹์€ ์˜ˆ๋กœ ์‚ผ์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ฐ€์ž…/๋กœ๊ทธ์ธ์ด ์›น 1.0 ์Šคํƒ€์ผ์„ ์™„์ „ํžˆ ์ œ์ถœํ•˜๋„๋ก ๋ณ€๊ฒฝํ•˜์—ฌ ์„œ๋ฒ„์— HTTP ์ „์šฉ ์ฟ ํ‚ค๋ฅผ ์„ค์ •(XSS๋ฅผ ํ†ตํ•œ JWT์— ๋Œ€ํ•œ ์•ก์„ธ์Šค ์ œ๊ฑฐ)ํ•œ ๋‹ค์Œ ์‚ฌ์šฉ์ž ๊ฐœ์ฒด๋ฅผ req ์ฒจ๋ถ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ „์ฒด ํ† ํฐ๋ณด๋‹ค ์ด๊ฒƒ์€ CSRF ์ทจ์•ฝ์ ์˜ ๊ฐ€๋Šฅ์„ฑ์„ ๋‚จ๊ธฐ์ง€๋งŒ ์ด๊ฒƒ์ด XSS๋ณด๋‹ค ์™„ํ™”ํ•˜๊ธฐ๊ฐ€ ๋” ๊ฐ„๋‹จํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค(์•„๋‹ˆ์š”?). ์ด๊ฒƒ์˜ ์ข‹์€ ๋ถ€์ž‘์šฉ์€ ํด๋ผ์ด์–ธํŠธ ์•ฑ์ด ๋งน์„ธ ์„œ๋น„์Šค๋ฅผ ํ†ตํ•ด ๋กœ๊ทธ์ธํ•  ๋•Œ ๊ฑฐ์˜ ๋™์ผํ•œ ํ๋ฆ„์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‚˜๋Š” ๋˜ํ•œ Page HoC์˜ getInitialProps ์—์„œ ์ฟ ํ‚ค๋ฅผ ๊ตฌ๋ฌธ ๋ถ„์„ํ•˜์—ฌ "๋ฏธ๋“ค์›จ์–ด"(๋”ฐ๋ผ์„œ ์‚ฌ์šฉ์ž ์ •์˜ ์„œ๋ฒ„๊ฐ€ ํ•„์š”ํ•จ)๋ฅผ ํ”ผํ•  ์ˆ˜ ์žˆ์Œ์„ ๋‚˜์ค‘์— ์•Œ์•˜์Šต๋‹ˆ๋‹ค.

@possibilities '๋น„๋ฐ€' ํŽ˜์ด์ง€๋Š” ํŽ˜์ด์ง€์ผ

์˜ˆ, ์„œ๋ฒ„ ์ธก ๋ฆฌ๋””๋ ‰์…˜์„ ์ˆ˜ํ–‰ํ•˜๋ ค๋ฉด ์—ฐ๊ฒฐํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

BTW, ๋‚˜๋Š” github์œผ๋กœ ๋กœ๊ทธ์ธํ•˜๋Š” ์ดˆ๊ธฐ ํ”„๋กœ์ ํŠธ๋กœ ๋‹ค์‹œ ์‚ฐ๋งŒ ํ•ด์กŒ์Šต๋‹ˆ๋‹ค. ํ๋ฆ„์€ ๋ณด์•ˆ์— ๋” ์‹ ๊ฒฝ์„ ์“ฐ๋Š” ๊ฒƒ๊ณผ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค(์ฆ‰, XSS๋ฅผ ํ†ตํ•ด oauth ํ† ํฐ์ด ๋…ธ์ถœ๋˜๋Š” ๊ฒƒ์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด ํด๋ผ์ด์–ธํŠธ์—์„œ ๋น„๋ฐ€์„ ๋…ธ์ถœํ•˜์ง€ ์•Š์Œ). ์ ์ ˆํ•œ ์•ฑ์— ๋ฌถ์—ฌ ์žˆ์ง€๋งŒ ๊ด€์‹ฌ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ์ผ๋ฐ˜์ ์œผ๋กœ oauth ํ๋ฆ„์— ์œ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์œผ๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@possibilities ๊ฐ€์žฅ ์ตœ์†Œํ•œ์˜(๊ทธ๋Ÿฌ๋‚˜ ์ ์ ˆํ•œ) with-auth ์˜ˆ์ œ๋กœ PR์ด ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค๋ฉด ๊ต‰์žฅํ•œ ๋„์›€์ด ๋  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค ๐Ÿ™‚ ์ €๋Š” ์•ฑ์—์„œ auth ์ž‘์—…์„ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค(https://github.com/relatenow/ ๊ด€๋ จ) ๊ทธ๋Ÿฌ๋‚˜ ํ˜„์žฌ๋Š” ํด๋ผ์ด์–ธํŠธ ์ธก(localStorage) ์ „์šฉ์ž…๋‹ˆ๋‹ค.

@sedubois ๋‚˜๋Š” passwordless.net https://github.com/zeit/next.js/pull/646 ์„ ์‚ฌ์šฉํ•˜๋Š” ํ™๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์ง€๋งŒ ์ธ์ฆ ์„œ๋ฒ„๋ฅผ ๋‹ค๋ฅธ ๊ณณ์œผ๋กœ ์˜ฎ๊น๋‹ˆ๋‹ค.

graphql์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ์–ด๋–ป์Šต๋‹ˆ๊นŒ?

Apollo๋Š” graphql ์ธ์ฆ์˜ ์˜ˆ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

https://dev-blog.apollodata.com/a-guide-to-authentication-in-graphql-e002a4039d1

์—ฌ๊ธฐ์„œ ์šฐ๋ฆฌ๋Š” graphql ์š”์ฒญ์„ ์ธ์ฆํ•˜์ง€๋งŒ ์šฐ๋ฆฌ์˜ ๊ฒฝ์šฐ์— ์ ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ graphql์€ ๊ตฌํ˜„ ๋ฐ ๋…ผ๋ฆฌ๋ฅผ ์ถ”์ƒํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•”ํ˜ธ ์—†๋Š”, auth0 ๋˜๋Š” ์šฐ๋ฆฌ๊ฐ€ ์„ ํ˜ธํ•˜๋Š” ๋‹ค๋ฅธ ๊ฒƒ๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@imronunciable ์ฐธ๊ณ ๋กœ ๊ท€ํ•˜์˜ ์˜ˆ์—์„œ๋Š” ์•”ํ˜ธ๊ฐ€ ์—†๋Š” ๊ฒƒ์„ ์—†์• ๊ณ  ์‹ถ๊ธฐ ๋•Œ๋ฌธ์— ์„ธ์…˜์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•„์ง ๋ชจ๋ฆ…๋‹ˆ๋‹ค. ๋‚ด ์•ฑ์—์„œ @iaincollins ' ์˜ˆ์ œ๋ฅผ ์ ์šฉํ•ด

๋‚ด ์š”๊ตฌ ์‚ฌํ•ญ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ์•ˆ์ „ํ•œ
  • ์„œ๋ฒ„ ์ธก ์ธ์ฆ ํ›„ ํด๋ผ์ด์–ธํŠธ ์ธก ์ธ์ฆ
  • Facebook ๋ฐ ๋กœ๊ทธ์ธ/๋น„๋ฐ€๋ฒˆํ˜ธ ๋ชจ๋‘ ์ง€์›
  • ์ธ์ฆ ์ œ๊ณต์ž๋Š” ์‰ฝ๊ฒŒ ๋ณ€๊ฒฝ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • Graphcool์—์„œ ์‚ฌ์šฉ์ž ์ƒ์„ฑ
  • Graphcool์— ๋Œ€ํ•œ ํ›„์† GraphQL ์š”์ฒญ ์ธ์ฆ

๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ์—ฌ๊ธฐ์— ๋„์›€์ด๋˜๋Š” ๊ฒฝ์šฐ ์„œ๋ฒ„ ์ธก ์ธ์ฆ์ด์žˆ๋Š” ๋‚ด ์•ฑ์ž…๋‹ˆ๋‹ค ๐Ÿ™‚

์ธ์ฆ์€ Next.js ์„œ๋ฒ„์—์„œ ๋ถ„๋ฆฌ๋˜์–ด์•ผ ํ•˜์ง€๋งŒ ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ์ด์— ๋Œ€ํ•ด ์˜๊ฐ์„ ์ฃผ๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค... ๋˜ํ•œ CSRF๋กœ๋ถ€ํ„ฐ ์ œ๋Œ€๋กœ ๋ณดํ˜ธ๋˜๋Š”์ง€ ํ™•์‹คํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์ด ๋‚ด๊ฐ€ ํ•œ ์ผ์ž…๋‹ˆ๋‹ค.

  • ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•  ๋•Œ ํด๋ผ์ด์–ธํŠธ ์ธก ์ž๋ฐ” ์Šคํฌ๋ฆฝํŠธ๋Š” ์ธ์ฆ ์ „๋‹ฌ์ž ํ† ํฐ์„ ํฌํ•จํ•˜๋Š” ๋ธŒ๋ผ์šฐ์ €์— ์ฟ ํ‚ค๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  • ์ธ์ฆ๋œ ์š”์ฒญ์„ ํ•  ๋•Œ ์„œ๋ฒ„ ์ธก(next.js) ์ฝ”๋“œ๋Š” ๋ธŒ๋ผ์šฐ์ €์˜ ์š”์ฒญ ํ—ค๋”์—์„œ ์ „๋‹ฌ์ž ํ† ํฐ์„ ์ฝ๊ณ  ์ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋Œ€์‹ ํ•˜์—ฌ api ์„œ๋ฒ„์— ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ XSS(๋ฐ˜์‘์ด ๊ทธ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋งŽ์ด ํ•˜๋Š” ๊ฒฝ์šฐ์—๋„) ๋ฐ CSRF ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•˜์ง€๋งŒ ๊ฐ„๋‹จํ•˜๊ณ  SSR๊ณผ ํ•จ๊ป˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

์š”์ฒญ์— ์ถ”๊ฐ€ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด

graph.cool + apollo + jwt + auth0 + next.js, ๊ทธ ์ค‘ ์ฒ˜์Œ 4๊ฐœ ๋ถ€๋ถ„์€ ์ด๋ฏธ https://github.com/graphcool-examples/react-apollo-auth0-example ์—์„œ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๊ท€ํ•˜์˜ ์˜ˆ์—์„œ @balupton ์‚ฌ์šฉ์ž๊ฐ€ ์•„์ง ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๋Š” ๋™์•ˆ ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ฉ๋‹ˆ๊นŒ? ์„ธ์…˜์ด ๋ฐฉ๊ธˆ ๋๋‚ฌ์Šต๋‹ˆ๊นŒ, ์•„๋‹ˆ๋ฉด ์–ด๋–ป๊ฒŒ ๋“  ๊ฐฑ์‹ ๋˜์—ˆ์Šต๋‹ˆ๊นŒ?

@nmaro ๋Š” ๋‚ด๊ฐ€ ์“ด ๊ธ€์ด ์•„๋‹ˆ๋ผ ์ž˜ ๋ชจ๋ฆ…๋‹ˆ๋‹ค. ๊ฑฐ๊ธฐ์— ๋ฌผ์–ด๋ณด๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ข‹์Šต๋‹ˆ๋‹ค.

๋‚ด ์•ฑ์˜ ๊ฒฝ์šฐ next.js ๋ฐ auth0๊ณผ ํ•จ๊ป˜ ์ธ์ฆ์„ ๋ฐ›์€ ๋‹ค์Œ ์ „๋‹ฌ์ž ํ† ํฐ์„ ํ™•์ธํ•˜๋Š” zeit/micro API ์„œ๋ฒ„๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

2์›”์— ์˜คํ”ˆ์†Œ์Šคํ™”ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

next.js๊ฐ€ ๋”ฐ๋ฅด๊ณ  ์žˆ๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ์ฒ ํ•™(ํ•œ ๊ฐ€์ง€๋งŒ ํ•˜๊ณ  ์ž˜ ํ•˜๊ธฐ)์— ๋”ฐ๋ผ ๋…ธ๋“œ์šฉ ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ์‚ฌ์šฉ์ž ๊ณ„์ • ์‹œ์Šคํ…œ์˜ ๊ณจ๊ฒฉ์„ ๊ฐœ๋ฐœํ–ˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ ์ฒ ํ•™์„ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค: https://medium.com/the-ideal-system/ooth-user-accounts-for-node-js-93cfcd28ed1a#.97kyfg4xg

github ํ”„๋กœ์ ํŠธ๋Š” ์—ฌ๊ธฐ: https://github.com/nmaro/ooth/

๋ชฉํ‘œ๋Š” node.js์™€ ์•„์ฃผ ์ž˜ ์–ด์šธ๋ฆฌ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ตฌ์กฐ์ธ ๋ณ„๋„์˜ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋กœ ์‚ฌ์šฉํ•˜๊ธฐ์— ์ด์ƒ์ ์ธ ํ™•์žฅ ๊ฐ€๋Šฅํ•˜๊ณ  ๋…๋ฆฝ์ ์ธ ์ธ์ฆ + ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ ์„œ๋น„์Šค๋ฅผ ๊ฐ–๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด๋ฉ”์ผ+๋น„๋ฐ€๋ฒˆํ˜ธ ์ธ์ฆ์˜ ์˜ˆ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. https://github.com/nmaro/ooth/tree/master/examples/ooth
์ด๋ฏธ Facebook ๋ฐ Google auth(ooth-facebook ๋ฐ ooth-google)์šฉ ํŒจํ‚ค์ง€๊ฐ€ ์ค€๋น„๋˜์–ด ์žˆ์œผ๋ฉฐ ๊ฐ๊ฐ ์—ฌ๊ถŒ-ํŽ˜์ด์Šค๋ถ ๋ฐ ์—ฌ๊ถŒ-๊ตฌ๊ธ€์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ตœ๋Œ€ํ•œ ๋นจ๋ฆฌ next.js ํ†ตํ•ฉ ์˜ˆ์ œ๋ฅผ ๊ฒŒ์‹œํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ž์œ ๋กญ๊ฒŒ ํ† ๋ก ์— ์ฐธ์—ฌํ•˜๊ณ  ๊ธฐ์—ฌํ•˜์‹ญ์‹œ์˜ค.

ํ”Œ๋Ÿฌ๊ทธ์— ๋Œ€ํ•ด ์œ ๊ฐ์Šค๋Ÿฝ๊ฒŒ ์ƒ๊ฐํ•˜์ง€๋งŒ ๋” ํฐ ์ด์ต์„ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์•„์ง ๋…ธ๋“œ์— ๋Œ€ํ•œ ์ข‹์€ ์†”๋ฃจ์…˜์ด ์—†๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ๋ฐ”๋กœ ๊ทธ๋Ÿฌํ•œ ๊ฒƒ์„ ์›ํ•˜๋Š” ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ์ ํ•ฉํ•œ ์ฒญ์ค‘์ž…๋‹ˆ๋‹ค. ํ‰ํ™”

ํ•œํŽธ... ๋‹ค์Œ์€ ์“ฐ๊ธฐ ์ž‘์—…์— ๋Œ€ํ•ด์„œ๋งŒ JWT-Token์œผ๋กœ ์ธ์ฆ์ด ํ•„์š”ํ•œ graphql API์˜ ์˜ˆ์ž…๋‹ˆ๋‹ค. ๋ณธ์ธ์ด ์„ ํ˜ธํ•˜๋Š” ์ธ์ฆ ๋ฐฉ์‹์œผ๋กœ ์ž์œ ๋กญ๊ฒŒ ์‚ฌ์šฉํ•˜์„ธ์š” :)

https://github.com/nmaro/ooth/tree/master/examples/graphql-api-with-auth

oAuth ์ง€์›์„ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด https://nextjs-starter.now.sh ๋ฅผ ์—…๋ฐ์ดํŠธํ–ˆ์Šต๋‹ˆ๋‹ค.

screen shot 2017-02-10 at 05 03 19

  • ๊ทธ๊ฒƒ์€ ์ต์Šคํ”„๋ ˆ์Šค ์„ธ์…˜(์ด์ „๊ณผ ๊ฐ™์ด)๊ณผ ํ•จ๊ป˜ Passport for oAuth๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • Facebook, Google ๋ฐ Twitter+ oAuth์— ๋Œ€ํ•œ ์ง€์›์ด ์žˆ์œผ๋ฉฐ ๋” ์‰ฝ๊ฒŒ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค( AUTHENTICATION.md ๋ฐ route/auth-passport.js ์ฐธ์กฐ ).
  • ๋ฒ”์šฉ ํด๋ผ์ด์–ธํŠธ/์„œ๋ฒ„ ์„ธ์…˜ ์‹œ์Šคํ…œ(CSRF ํ† ํฐ, HTTP ์ „์šฉ ์ฟ ํ‚ค๋ฅผ ํ†ตํ•œ XSS ๋ณดํ˜ธ, Mongo, SQL DB, Redshift ๋“ฑ์„ ์ง€์›ํ•˜๋Š” ORM ๊ณ„์ธต ํฌํ•จ)์„ ์ด๋ฉ”์ผ ๋กœ๊ทธ์ธ์œผ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐœ๋ฐœ์ž ํฌํ„ธ์—์„œ oAuth๋ฅผ ๊ตฌ์„ฑํ•œ ๊ฒฝํ—˜์ด ๊ทธ ์–ด๋Š ๋•Œ๋ณด๋‹ค ๋”์ฐํ•˜๋‹ค๊ณ  ๋ณด๊ณ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์ด์ƒํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ  ๋””๋ฒ„๊ทธํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค).

oAuth์˜ ํŠน์„ฑ - db+sessions+passport ๋ฐ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๊ฐ€ ๊ธด๋ฐ€ํ•˜๊ฒŒ ์ž‘๋™ํ•ด์•ผ ํ•˜๋ฉฐ, ์„ธ์…˜์ด ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๋ชจ๋‘์—์„œ ์ž‘๋™ํ•ด์•ผ ํ•˜๋ฉฐ ์ด๊ฒƒ์ด ๋ฒ”์šฉ ๋ Œ๋”๋ง๊ณผ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ - ๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚˜๊ณ  ์žˆ๋Š”์ง€ ์•Œ์•„ ๋‚ด๋ ค๊ณ  ๋…ธ๋ ฅํ•˜๊ณ  ์žˆ์ง€๋งŒ ํด๋ผ์ด์–ธํŠธ ๋…ผ๋ฆฌ์—๋Š” oAuth ํŠน์ • ๊ตฌ์„ฑ์ด ์—†์œผ๋ฏ€๋กœ ๋„ˆ๋ฌด ์ง€์ €๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋‚˜๋Š” ๊ทธ๊ฒƒ์ด ํ›จ์”ฌ ๋” ์ž‘์ง€ ์•Š์„ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜์ง€๋งŒ ์ธ์ฆ๋งŒ์„ ๋ณ„๋„์˜ ์˜ˆ๋กœ ๋‚˜๋ˆ„๊ฒŒ ๋˜์–ด ๊ธฐ์ฉ๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ์›ํ•œ๋‹ค๋ฉด ์ข‹์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” ์•„๋งˆ๋„ (๊ณ„์ • ๊ด€๋ฆฌ ํŽ˜์ด์ง€์™€ ๊ฐ™์€) ์–ด๋Š ์‹œ์ ์—์„œ ์˜ˆ์ œ์— ์กฐ๊ธˆ ๋” ์ถ”๊ฐ€ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์•„๋งˆ๋„ ๋ฌธ์„œ์— ๋” ๋งŽ์€ ๊ฒƒ์„ ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋†€๋ผ์šด ์ž‘ํ’ˆ! ์ธ์ฆ์„ ๋ถ„ํ• ํ•˜๊ณ  next.js ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ๋Œ€๋‹จํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.:heart:

๋‚˜๋„ ํ•  ์ˆ˜ ์žˆ๋‹ค btw ๐Ÿ‘๐Ÿป

์•ž์œผ๋กœ 2์ฃผ ์ •๋„ ์‹œ๊ฐ„์ด ์—†์„ ๊ฒƒ์ด๋ฏ€๋กœ ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ์›ํ•œ๋‹ค๋ฉด ๊ทธ๋ ‡๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‚˜๋Š” ๊ทธ๊ฒƒ์„ ๋ฆฌํŒฉํ† ๋งํ•˜๊ณ  ๊ทธ๊ฒƒ์ด ๋‹จ์ง€ ๋ชจ๋“ˆ๋กœ ์ถ•์†Œ๋  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ (๋กœ๊ทธ์ธ ๋ฒ„ํŠผ๊ณผ ๊ฐ™์€ ๊ฐ„๋‹จํ•œ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋…ธ์ถœํ•˜๊ณ  ํฌํ•จํ•  ์–‘์‹์— ๋กœ๊ทธ์ธ) ์ •๋ง ๋ฉ‹์ง„ ์ด์ „ ํด๋ผ์ด์–ธํŠธ ์ธก ์ „์šฉ ์˜ˆ์ œ์—์„œ ์˜๊ฐ์„ ์–ป๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. @impronuncible๋กœ.

์—…๋ฐ์ดํŠธ: ์ €๋Š” ์‹ค์ œ๋กœ ๋– ๋‚  ๊ฒƒ์ด๋ฏ€๋กœ ๊ฐˆ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋Œ์•„์˜ฌ ๋•Œ ๊ทธ ์ผ์„ ํ•˜๋Š” ๊ฒƒ์„ ๋ณด๊ฒŒ ๋˜์–ด ๊ธฐ์ฉ๋‹ˆ๋‹ค!

auth0/react ๋น ๋ฅธ ์‹œ์ž‘ ๊ฐ€์ด๋“œ๋ฅผ ์•ฝ๊ฐ„ ์ˆ˜์ •ํ–ˆ์ง€๋งŒ lock.show() ํ˜ธ์ถœํ•˜๋ฉด ์•ฑ ๋ถˆ๋งŒ ์‚ฌํ•ญ:

์žกํžˆ์ง€ ์•Š์€ ์˜ค๋ฅ˜: addComponentAsRefTo(...): ReactOwner๋งŒ ์ฐธ์กฐ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ตฌ์„ฑ ์š”์†Œ์˜ render ๋ฉ”์„œ๋“œ ๋‚ด์—์„œ ์ƒ์„ฑ๋˜์ง€ ์•Š์€ ๊ตฌ์„ฑ ์š”์†Œ์— ์ฐธ์กฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ React์˜ ์—ฌ๋Ÿฌ ๋ณต์‚ฌ๋ณธ์ด ๋กœ๋“œ๋˜์—ˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ท€ํ•˜์˜ ์˜ˆ์™€ ๊ด€๋ จํ•˜์—ฌ @iaincollins @timneutkens , ๋‚ด๊ฐ€ ํ‹€๋ ธ๋‹ค๋ฉด ์ •์ •ํ•ด ์ฃผ์‹ญ์‹œ์˜ค.

์ด ์˜ˆ์—์„œ๋Š” httpOnly ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„ธ์…˜ ํ† ํฐ์„ ์ €์žฅํ•˜์—ฌ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์‚ฝ์ž… ๊ณต๊ฒฉ(XSS)์œผ๋กœ๋ถ€ํ„ฐ ์•ˆ์ „ํ•˜๊ฒŒ ๋ณดํ˜ธํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ CSRF ๊ณต๊ฒฉ์œผ๋กœ๋ถ€ํ„ฐ ๋ณดํ˜ธํ•˜๊ธฐ ์œ„ํ•ด ๋กœ์ปฌ ์ €์žฅ์†Œ์— csrf ํ† ํฐ์„ ์ €์žฅํ•ด์•ผ ํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๊ธฐ์ˆ  ์กฐํ•ฉ์ด ์‚ฌ๋ฌผ์„ ์•ˆ์ „ํ•˜๊ฒŒ ๋งŒ๋“ค์–ด ์‚ฌ์šฉ์ž/๊ฐœ๋ฐœ์ž๋ฅผ ์˜ค๋„ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ธฐ๋ณธ ๊ฐ€์ •์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ณต๊ฒฉ์ž๋Š” ์—ฌ์ „ํžˆ ํŽ˜์ด์ง€(XSS)์— javascript๋ฅผ ์‚ฝ์ž…ํ•˜๊ณ  csrf ํ† ํฐ์„ ์ฝ๊ณ  ์ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ API์— ๋Œ€ํ•œ ์ธ์ฆ๋œ(์ฟ ํ‚ค) ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฝ์–ด๋ณด๊ธฐ์—์„œ ์–ธ๊ธ‰ํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

์•ˆ๋…•ํ•˜์„ธ์š” @davibe

์•„์•„, ํ˜„์žฌ ์„ธ์…˜ ์ฟ ํ‚ค์™€ ๋ณ„๋„์˜ ํšŒ์ „ CSRF ํ† ํฐ๋ณด๋‹ค ๊ธฐ์ˆ ์ ์œผ๋กœ ๋” ๋‚˜์€ ์ ‘๊ทผ ๋ฐฉ์‹์€ ์—†์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์‹ค์ œ๋กœ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋ชจ๋ธ์— ๋Œ€์ฒด๋กœ ๋งŒ์กฑํ•œ๋‹ค๊ณ  ๋งํ•ด์•ผ ํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. (์‹ค์ œ ๊ตฌํ˜„์ด ํ•ญ์ƒ ๊ฐœ์„ ๋  ์ˆ˜ ์žˆ๋”๋ผ๋„).

๋ธŒ๋ผ์šฐ์ € ์ง€๋ฌธ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๊ณ  ์„ธ์…˜ ํ† ํฐ์ด ๋” ์ž์ฃผ ํšŒ์ „ํ•  ์ˆ˜ ์žˆ๊ณ (์ง€๊ธˆ์€ ์ž๋™์ธ์ง€ ์žŠ์–ด๋ฒ„๋ ธ์Šต๋‹ˆ๋‹ค.) CSRF ํ† ํฐ์€ param ๋Œ€์‹  ํ—ค๋”๊ฐ€ ๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ฟ ํ‚ค๋Š” ์‹ค์ œ๋กœ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋งŒ SSL์ด์–ด์•ผ ํ•˜๋ฉฐ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋กœ๊ทธ์ธ ํ† ํฐ์˜ ๋งŒ๋ฃŒ ๋‚ ์งœ๋Š” AFAICS์ด์ง€๋งŒ ๋ณด์•ˆ ๋ชจ๋ธ์ด ์ง„ํ–‰๋˜๋Š” ํ•œ ๊ฐœ์„ ์˜ ์—ฌ์ง€๊ฐ€ ์ƒ๋‹นํžˆ ์ œํ•œ๋˜์–ด ์žˆ์œผ๋ฉฐ ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ์›ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์•ฑ์— ์‚ฝ์ž…ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ ์ถ”๊ฐ€ ๋ณดํ˜ธ๋ฅผ ์‹ค์ œ๋กœ ๋ถ€์—ฌํ•˜๋Š” ๊ฒƒ์€ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ repo ๊ฐœ์„ ์„ ์œ„ํ•œ ๋ฌธ์ œ๋กœ ์ด๋Ÿฌํ•œ ๊ฒƒ๋“ค์„ ์ž์œ ๋กญ๊ฒŒ ์ œ๊ธฐํ•˜์‹ญ์‹œ์˜ค.

๊ณ„์ • ์ƒํƒœ ์ˆ˜์ • ์˜ต์…˜(ํ˜„์žฌ ์—†์Œ)์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๊ถŒํ•œ ์—†์ด ์„œ๋ฒ„์—์„œ ๋ณ€๊ฒฝ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์„ ๋” ์–ด๋ ต๊ฒŒ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์ˆ˜ํ–‰ํ•˜๊ธฐ ์ „์— ๋ณด์•ˆ ๋ฌธ์ž๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์˜ˆ์ œ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ชจ๋“  ์ผ์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ˜„์žฌ ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚ฌ์Šต๋‹ˆ๋‹ค.

์„ธ์…˜ ํ† ํฐ์„ ๋กœ์ปฌ ์ €์žฅ์†Œ์— ์ €์žฅํ•˜๋ฉด ๋ณด์•ˆ์ด ํ˜„์ €ํžˆ ๋–จ์–ด์ง€๊ณ  ์‚ฌ์–‘์˜ ์˜๋„๋œ ์šฉ๋„์— ์–ด๊ธ‹๋‚˜๋ฏ€๋กœ ๊ฐœ์ธ์ ์œผ๋กœ ์ €๋Š” ์ฐฌ์„ฑํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ CSRF ํ”„๋กœ์ ์…˜์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ผ์„ ๋‹จ์ˆœํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์— ๊ฐ์‚ฌํ•˜์ง€๋งŒ ์‚ฌ๋žŒ๋“ค์€ ์•„๋งˆ๋„ ๋งค์šฐ ๊ฐ„๋‹จํ•˜๋ฏ€๋กœ ์˜ˆ์ œ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋„ˆ๋ฌด ์–ด์ƒ‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•˜๋‚˜๋งŒ ์ œ๊ณตํ•˜๋ ค๊ณ  ํ–ˆ์Šต๋‹ˆ๋‹ค. :-)

๋‚˜๋Š” ๊ทธ๊ฒƒ์ด ์–ผ๋งˆ๋‚˜ ํ•„์—ฐ์ ์œผ๋กœ ์–ด์ƒ‰ํ•œ์ง€ ๋„ˆ๋ฌด ์‹ซ๊ณ  ๊ทธ๊ฒƒ์„ ๋ชจ๋“ˆ๋กœ ๋ฐ”๊พธ๋ ค๋Š” ์‹œ๋„๋ฅผ ํฌ๊ธฐํ•˜์ง€ ์•Š์•˜๋‹ค๋Š” ์ ์—์„œ ์ „์ ์œผ๋กœ ๋‹น์‹ ๊ณผ ํ•จ๊ป˜ํ•ฉ๋‹ˆ๋‹ค.

ํ .. ์ดํ•ดํ•ฉ๋‹ˆ๋‹ค.
์ด ๋ฌธ์ œ๋Š” ์ €์—๊ฒŒ ๋งค์šฐ ์œ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ Auth0 https://auth0.com/blog/cookies-vs-tokens-definitive-guide/ ์˜ ๊ด€์ ์ž…๋‹ˆ๋‹ค.
๊ทธ๋“ค์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ฟ ํ‚ค๋ฅผ ํ”ผํ•˜๋„๋ก ์ œ์•ˆํ•˜์ง€๋งŒ ์ฒซ ๋ฒˆ์งธ ํŽ˜์ด์ง€ ๋กœ๋“œ์—์„œ SSR์„ ์ œ์™ธํ•  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

์•ˆ๋…• ๋ชจ๋‘๋“ค!

์ €๋Š” ๋˜ํ•œ next.js๋กœ ์ธ์ฆ ์ž‘์—…์„ ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ ์ด ๋ฌธ์ œ์˜ ๋Œ“๊ธ€, ํŠนํžˆ @davibe + repo์™€ @timneutkens ์˜ PR์ด ํฐ ๋„์›€์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋‚ด ์†”๋ฃจ์…˜์€ ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

  • ๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ•˜๋ฉด redux ๋ฆฌ๋“€์„œ๋Š” react-cookie๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ† ํฐ๊ณผ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ
  • ํ•ด๋‹น ์ •๋ณด๊ฐ€ ํ•„์š”ํ•œ ๋ชจ๋“  ํŽ˜์ด์ง€/๊ตฌ์„ฑ ์š”์†Œ๋Š” @timneutkens with-session.js์™€ ์œ ์‚ฌํ•œ ๊ณ ์ฐจ ๊ตฌ์„ฑ ์š”์†Œ๋กœ ๋ž˜ํ•‘๋ฉ๋‹ˆ๋‹ค. SSR์ธ ๊ฒฝ์šฐ ํ† ํฐ์„ ctx.req.headers.cookie์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๋ธŒ๋ผ์šฐ์ € ๋ฌธ์„œ์—์„œ ํ† ํฐ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค(๋ฐ˜์‘ ์ฟ ํ‚ค ๋กœ๋“œ ๋ฐฉ๋ฒ• ์‚ฌ์šฉ).
  • ํ† ํฐ์ด ์žˆ์œผ๋ฉด ์š”์ฒญํ•  ๋•Œ๋งˆ๋‹ค Bearer/Authorization ํ—ค๋”์— ํ† ํฐ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋„์ปค ์ปจํ…Œ์ด๋„ˆ์—์„œ ์‹คํ–‰ ์ค‘์ธ ์ž์ฒด JWT ํ† ํฐ ์ธ์ฆ ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.
with-auth ์˜ˆ์ œ์˜ ์ผ๋ถ€๋กœ ๊ฐ„๋‹จํ•œ JWT ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด ๋” ์‰ฌ์šธ๊นŒ์š”? ๋”ฐ๋ผ์„œ server.js๋ฅผ ํ•ดํ‚นํ•˜๋Š” ๊ฒƒ์„ ํ”ผํ•˜๊ณ  ์ž ์žฌ์ ์œผ๋กœ next.js ๋ฒ ์ดํฌ์ธ ํ•ซ ๋ฆฌ๋กœ๋”ฉ, ssr ๋ฐ ๋ผ์šฐํŒ…์˜ ์ด์ ์„ ์žƒ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

๋˜ํ•œ ์ด ์ ‘๊ทผ ๋ฐฉ์‹์ด CSRF/XSS ์ธก๋ฉด์—์„œ ์•ˆ์ „ํ•œ์ง€ ํ™•์‹คํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์˜๊ฒฌ์„ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค.

์ง€๊ธˆ๊นŒ์ง€ ๋†€๋ผ์šด ์ผ์„ ํ•ด์ฃผ์‹  ๋ชจ๋“  ๋ถ„๋“ค๊ป˜ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ์ €๋Š” ์ด ํ”„๋กœ์ ํŠธ์˜ ์—ด๋ ฌํ•œ ํŒฌ์ž…๋‹ˆ๋‹ค!

@jcsmesquita ์ €๋Š”

์ด๊ฒƒ์€ ์œ ์šฉํ•œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค: https://hub.docker.com/r/rabbotio/nap/~/dockerfile/

@subsumo ์–ธ๊ธ‰ NAP ๋Š” ์ œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์›น์„ ํ†ตํ•œ ์ธ์ฆ์€ @iaincollins nextjs-starter๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ณ  ๊ธฐ๋ณธ ํด๋ผ์ด์–ธํŠธ ๋กœ๊ทธ์ธ์— ํ† ํฐ์œผ๋กœ ๋ฐ˜์‘ํ•ฉ๋‹ˆ๋‹ค.

@timneutkens ์ž‘์—… ์ค‘์ธ ์†”๋ฃจ์…˜์—์„œ ๋ณ„๋„์˜ ์„œ๋น„์Šค์— ๋Œ€ํ•ด ์ธ์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

ํ˜„์žฌ ์ธ์ฆ ์˜ˆ์ œ ํ’€ ์š”์ฒญ https://github.com/zeit/next.js/pull/1141์„ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค.
next.js ์„œ๋ฒ„์— ๋Œ€ํ•ด์„œ๋งŒ ์ธ์ฆ ์„ ํ—ˆ์šฉํ•˜์ง€๋งŒ ๋ณ„๋„์˜ ์„œ๋น„์Šค์— ๋Œ€ํ•ด์„œ๋Š” ๋™ํ˜•์ด ์•„๋‹Œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ฆ‰, next.js ์„œ๋ฒ„์™€ ์‹ค์ œ ์•ฑ API(์˜ˆ: REST ๋˜๋Š” GraphQL)๋ฅผ ๋ถ„๋ฆฌํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„๊ฐ€ API์— ๋Œ€ํ•ด ์ธ์ฆํ•  ์ˆ˜ ์žˆ๊ธฐ๋ฅผ ์›ํ•ฉ๋‹ˆ๋‹ค. ์ด ์†”๋ฃจ์…˜์ด ์‹ค์ œ๋กœ ๋„์›€์ด ๋˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

ํ๋ฆ„์„ ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.

์—”ํ‹ฐํ‹ฐ:

  • ํด๋ผ์ด์–ธํŠธ(C)
  • Next.js ์„œ๋ฒ„(S)
  • API(A)

๋ชฉํ‘œ๋Š” 3๊ฐœ์˜ ์ฟ ํ‚ค ๊ธฐ๋ฐ˜ ์„ธ์…˜์„ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

  1. CS
  2. ์บ˜๋ฆฌํฌ๋‹ˆ์•„
  3. ์‚ฌ

Session 1)์€ Client๊ฐ€ Server๋ฅผ ์ธ์‹ํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ด๊ณ , 2) 3)์€ Client์™€ Server๊ฐ€ ๊ฐ๊ฐ์˜ ์„ธ์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ API์— ๋…๋ฆฝ์ ์œผ๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ํ๋ฆ„์ž…๋‹ˆ๋‹ค.

  1. CS๋Š” ์‰ฝ๊ฒŒ ์™„๋ฃŒ๋˜๋ฉฐ ์ธ์ฆ์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  2. CA๋Š” ์ธ์ฆ์œผ๋กœ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค(์˜ˆ: ์‚ฌ์šฉ์ž ์ด๋ฆ„/๋น„๋ฐ€๋ฒˆํ˜ธ ์‚ฌ์šฉ). ๋˜ํ•œ JWT๊ฐ€ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.
  3. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์— JWT๋ฅผ ์ œ๊ณตํ•œ ๋‹ค์Œ ๋ฒ„๋ฆฝ๋‹ˆ๋‹ค.
  4. SA๋Š” JWT๋กœ ์™„๋ฃŒ๋˜๊ณ  ํ๊ธฐ๋ฉ๋‹ˆ๋‹ค.

๋‹น์‹ ์˜ ์˜๊ฒฌ์€ ์–ด๋–ป์Šต๋‹ˆ๊นŒ? ๋” ์‰ฌ์šด ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๊นŒ? ํ•˜๋‚˜์˜ ์„ธ์…˜(CS)๋งŒ ํ•„์š”ํ•˜๋„๋ก API๋ฅผ next.js ์„œ๋ฒ„์™€ ๊ฒฐํ•ฉ๋œ ์ƒํƒœ๋กœ ์œ ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๊นŒ?

@rauchg ๋‚˜๋Š” ๋‹น์‹ ์„ ์‰ฝ๊ฒŒ ์†Œํ™˜ํ•˜์ง€ ์•Š์ง€๋งŒ ์ด๊ฒƒ์ด next.js๊ฐ€ ๊ฐ€์•ผ ํ•  ๋ฐฉํ–ฅ์— ๋Œ€ํ•œ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. "๋ณดํŽธ์ ์ธ ํ”„๋ก ํŠธ ์—”๋“œ"๋กœ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๋Š” API์™€ ๋ณ„๋„๋กœ ์‹คํ–‰ํ•ด์•ผํ•ฉ๋‹ˆ๊นŒ? ๊ทธ๋ ‡๋‹ค๋ฉด ์šฐ๋ฆฌ๋Š” ์ด๊ฒƒ์„ ๋ฐ”๋กœ์žก์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

@timneutkens ์ž‘์—… ์ค‘์ธ ์†”๋ฃจ์…˜์—์„œ ๋ณ„๋„์˜ ์„œ๋น„์Šค์— ๋Œ€ํ•ด ์ธ์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

๊ทธ ์ƒ๊ฐ์ด ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค. Next.js์—์„œ ๋‹ค๋ฅธ ๊ฒƒ๋“ค์„ ์ˆ˜์ •ํ•˜๋Š๋ผ ๋ฐ”๋นด์Šต๋‹ˆ๋‹ค. ์ตœ๋Œ€ํ•œ ๋นจ๋ฆฌ ๊ทธ๊ฒƒ์„ ๋‹ค์‹œ ์–ป์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‚ด ์•ฑ์˜ github-auth ๊ด€๋ จ ๋ถ€๋ถ„์„ ๊ฝค ์†Œํ™”ํ•˜๊ธฐ ์‰ฌ์šด ์˜ˆ์ œ๋กœ ๋‚˜๋ˆ„์—ˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ถ€ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ๋Š” ํฅ๋ฏธ๋กœ์šธ ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ฝ”๋“œ ๋˜๋Š” ํ๋ฆ„์— ๋Œ€ํ•œ ํ”ผ๋“œ๋ฐฑ์„ ์›ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค: https://github.com/possibilities/next-github-auth-example

์—…๋ฐ์ดํŠธ: ์˜ˆ์ œ ์•ฑ์„ ๋‹ค์Œ ์•ฑ์— "๋ฐฐํฌ"ํ•  ์ˆ˜ ์žˆ๋Š” ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ตฌ์„ฑ ์š”์†Œ ์ง‘ํ•ฉ์œผ๋กœ ๋ฆฌํŒฉํ† ๋งํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ›„์† ์กฐ์น˜: ooth ๋ฐ GraphQL API์™€์˜ ํ†ตํ•ฉ ์„ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

https://medium.com/the-ideal-system/ooth-user-accounts-for-node-js-93cfcd28ed1a#.ykoj1dhil

์ด๋Š” ๊ธฐ์กด ์ ‘๊ทผ ๋ฐฉ์‹, ์ฆ‰ next.js ์„œ๋ฒ„์— ๋Œ€ํ•œ ์ธ์ฆ

@timneutkens ๊ทธ

๋‚ด github ์ธ์ฆ ์˜ˆ์ œ ์•ฑ์„ ๋‹ค์Œ ์•ฑ์— "github ์ธ์ฆ์„ ์‚ญ์ œ"ํ•˜๊ธฐ ์œ„ํ•ด ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ๋ฐ ํŽ˜์ด์ง€ ๊ตฌ์„ฑ ์š”์†Œ ์„ธํŠธ๋กœ ๋ฆฌํŒฉํ† ๋งํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ ๋ฐ ๊ธฐ๋Šฅ ํ”ผ๋“œ๋ฐฑ์„ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค. https://github.com/possibilities/next-github-auth

๋‚˜๋Š” ์—ฌ๊ธฐ์—์„œ ๋ณด๋“œ๋ฅผ ๊ณ ์ •ํ•˜๊ณ  ๋‹ค์Œ์„ ์œ„ํ•ด ์ด๊ฒƒ์„ ๋ณด๋‹ค ์ผ๋ฐ˜์ ์ธ ์ธ์ฆ ํ”„๋ ˆ์ž„์›Œํฌ๋กœ ๊ณ„์† ๋ฐœ์ „์‹œํ‚ค๋Š” ๊ฒƒ์ด ํฅ๋ฏธ๋กœ์šธ ์ˆ˜ ์žˆ๋‹ค๊ณ  _์ƒ๊ฐ_ํ•ฉ๋‹ˆ๋‹ค.

@timneutkens
ํ•‘์„ ์ณ์„œ ์ฃ„์†กํ•˜์ง€๋งŒ ์ด๊ฒƒ์— ๋Œ€ํ•ด ์–ด๋–ค ์ง„์ „์ด ์žˆ์—ˆ์Šต๋‹ˆ๊นŒ? next.js๋กœ ์ธ์ฆ์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์™„์ „ํžˆ ์žƒ์–ด๋ฒ„๋ ธ์Šต๋‹ˆ๋‹ค.

@kolpav ๋‚˜๋Š” ๊ทธ๊ฒƒ์— ๋Œ€ํ•ด ์•ฝ๊ฐ„์˜ ์ž‘์—…์„ ์™„๋ฃŒ

next.js ์•ฑ์œผ๋กœ ์ธ์ฆ์„ ๋‹ฌ์„ฑํ•˜๋Š” ๋ช…ํ™•ํ•˜๊ณ  ์ž˜ ๋ฌธ์„œํ™”๋˜๊ณ  ์•ˆ์ „ํ•œ ๋ฐฉ๋ฒ•์€ ํ”„๋ ˆ์ž„์›Œํฌ๋กœ์„œ์˜ ์„ฑ๊ณต์— ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

์ธ์ฆ์ด ์—†๋Š” ์›น์„ ๋งˆ์ง€๋ง‰์œผ๋กœ ๊ตฌ์ถ•ํ•œ ๊ฒƒ์ด ์–ธ์ œ์ธ์ง€ ๊ธฐ์–ต์ด ๋‚˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
๋‚ด๊ฐ€ ์ƒ์ƒํ•˜๋Š” ๋Œ€๋ถ€๋ถ„์˜ ์‚ฌ๋žŒ๋“ค๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋‚˜๋„ ๋ฐฑ์—…๋œ ๋‚˜๋จธ์ง€ ๋ฐ์ดํ„ฐ์— ๋ณด์•ˆ ํ˜ธ์ถœ์„ ํ•˜๊ณ  ์‹ถ๊ธฐ ๋•Œ๋ฌธ์— JWT๊ฐ€ ํ™•์‹คํ•œ ์†”๋ฃจ์…˜์ธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ๋ฌธ์ œ์™€ PR ์‚ฌ์ด์— ๋„ˆ๋ฌด ๋งŽ์€ ๋…ผ์˜๊ฐ€ ์žˆ์–ด ์–ด๋””์„œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์•ผ ํ• ์ง€ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค!

@timneutkens
๋ฉ‹์ ธ์š”๐Ÿ‘ ๋‚จ์—๊ฒŒ ์•„์ฃผ ๊ท€ํ•œ ์ž๋ฃŒ๊ฐ€ ๋  ๊ฒƒ ๊ฐ™์•„์š”.

@camstuart @kolpav @jaredpalmer , @luisrudge , @impronunciable , @possibilities ๋ฐ ์ €์— ์˜ํ•ด JWT ๋ฐ HTTP ์ฟ ํ‚ค๋ฅผ ๋ชจ๋‘ ์‚ฌ์šฉํ•˜๋Š” oAuth ๋ฐ ์ด๋ฉ”์ผ ๊ธฐ๋ฐ˜ ์ธ์ฆ ์ง€์›์„ ํฌํ•จํ•˜์—ฌ ์œ„์˜ ๋ช‡ ๊ฐ€์ง€ ํ›Œ๋ฅญํ•˜๊ณ  ์ž‘๋™ํ•˜๋Š” ์˜ˆ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์ผ๋ถ€ ๋งํฌ๋ฅผ ๊ฐ•์กฐ ํ‘œ์‹œํ•˜๋ ค๋ฉด ์ฒดํฌ ์•„์›ƒํ•˜์‹ญ์‹œ์˜ค.

(๋งˆ์ดํฌ๋กœ ์ธ์ฆ ์˜ˆ์ œ๋Š” ์ข‹์•˜์ง€๋งŒ ์—…๋ฐ์ดํŠธ๊ฐ€ ํ•„์š”ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.)

์„ธ์…˜ ์ €์žฅ์†Œ ๊ตฌ์„ฑ ์š”์†Œ ๋ฐ ์„œ๋ฒ„ ๋…ผ๋ฆฌ ๋ถ„ํ• ์„ ํฌํ•จํ•˜์—ฌ ์œ„์—์„œ ์ถ”๊ฐ€ ์ฃผ์„๊ฐ€๊ฐ€ ์–ธ๊ธ‰ํ•œ ์ถ”๊ฐ€ ๊ฐœ์„ ์˜ ์—ฌ์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  Tim์ด ์ž‘์—…ํ•ด ์˜จ ์ž‘์—…์„ ๋”์šฑ ๋‹จ์ˆœํ™”ํ•˜๋Š” ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค.

์ธ์ฆ์„ ์œ„ํ•œ ๋‹จ์ˆœ์„ฑ์€ Universal ์•ฑ์˜ ์–ด๋ ค์šด ์˜์—ญ์ด์ง€๋งŒ ์œ„์˜ ์˜ˆ๋ฅผ ์‹คํ–‰ํ•˜๊ฑฐ๋‚˜ ๋ฐ๋ชจ๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•ด ๋ณด๊ณ  ํฐ ์†Œ๋ž€ ์—†์ด ์ž‘๋™ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

@iaincollins ์ข‹์€ ์˜ˆ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ (์˜ˆ๋ฅผ ๋“ค์–ด) ์Šคํƒ€ํ„ฐ ํ”„๋กœ์ ํŠธ๋ฅผ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? ๊ทธ๋ž˜์„œ ๋‚ด ์•ฑ์„ ๋งŒ๋“ค๊ณ  ์‹ถ๋‹ค๋ฉด. ์ด ์ €์žฅ์†Œ๋ฅผ ๋ณต์ œํ•ด์•ผ ํ•ฉ๋‹ˆ๊นŒ? ์•„๋‹ˆ๋ฉด ์Šคํƒ€ํ„ฐ ํ”„๋กœ์ ํŠธ ์ฒญํฌ์—์„œ ๋‚ด ์ฝ”๋“œ๋กœ ์ฝ”๋“œ๋ฅผ "๋ณต์‚ฌ-๋ถ™์—ฌ๋„ฃ๊ธฐ"ํ•ด์•ผ ํ•ฉ๋‹ˆ๊นŒ?

์Šคํƒ€ํ„ฐ ํ”„๋กœ์ ํŠธ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ•ฉ๋‹ˆ๊นŒ?

@iaincollins
ํŠนํžˆ ๋‹น์‹ ์˜ ์ข‹์€ ์˜ˆ.
ํ•˜์ง€๋งŒ ๊ทธ๋ž˜๋„ ์ธ์ฆ ๋„์žฅ์ด ์ฐํžŒ ์ธ์ฆ ์˜ˆ๋ฅผ ๋ณด๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค ๐Ÿ˜„ ๋ชจ๋“  ๋ˆˆ์ด ํ•œ ๋ฐฉํ–ฅ์„ ๊ฐ€๋ฆฌํ‚ค๋ฏ€๋กœ ์˜ค๋ฅ˜๋‚˜ ์‹ค์ˆ˜๊ฐ€ ๋ˆˆ์— ๋„์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ๋กœ์„œ๋Š” ์ž์ฒด ์ž‘์—… ์ธ์ฆ์ด ์žˆ์ง€๋งŒ ์–ผ๋งˆ๋‚˜ ์•ˆ์ „ํ•œ์ง€ ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค.

@kolpav ์— ๋™์˜ํ•ฉ๋‹ˆ๋‹ค. ์ž์‹ ์˜ ๋ณด์•ˆ์„ ๋กค๋งํ•˜๋Š” ๊ฒƒ์€ ๊นŒ๋‹ค๋กœ์šด ๋น„์ฆˆ๋‹ˆ์Šค์ž…๋‹ˆ๋‹ค. ์ „๋ฌธ๊ฐ€์—๊ฒŒ ๋งก๊ธฐ๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ข‹์Šต๋‹ˆ๋‹ค.

์ €๋Š” GraphQL๋กœ ์ธ์ฆ์„ ์‰ฝ๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ์Šคํƒ์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค: https://github.com/thebillkidy/MERGE-Stack

@salmazov ์‹œ์ž‘ํ•˜๊ณ  ์ดํ•ดํ•˜๋Š” ๋ฐ ์œ ์šฉํ•œ ๋ฐฉ๋ฒ•์„ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค. ์˜ˆ์ œ ๋˜๋Š” ์Šคํƒ€ํ„ฐ ํ”„๋กœ์ ํŠธ์—์„œ ์ง„ํ–‰ ์ƒํ™ฉ์„ ์ดํ•ดํ•˜๊ณ  ํฌํฌํ•œ ๋‹ค์Œ ๊ธฐ๋Šฅ๊ณผ ๊ด€๋ จ๋œ ์ฝ”๋“œ๋งŒ ๋‚จ์„ ๋•Œ๊นŒ์ง€ ๊ด€๋ จ์ด ์—†๋Š” ํ•ญ๋ชฉ์„ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹น์‹ ์ด ๊ตฌํ˜„ํ•˜๊ณ  ์‹ถ์€; ๊ทธ๋Ÿฐ ๋‹ค์Œ ํ•ด๋‹น ๊ธฐ๋Šฅ๋งŒ ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ๋กœ ์ด์‹ํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

@kolpav @camstuart ์ด ์Šค๋ ˆ๋“œ์—๋Š” ๋ช‡ ๊ฐ€์ง€ ์ผ๋ฐ˜์ ์ธ ์˜คํ•ด์™€ ์ ˆ์ถฉ์ ์„ ํญ๋กœํ•˜๋Š” ๊ฒƒ์„ ํฌํ•จํ•˜์—ฌ ๋‹ค์–‘ํ•œ ๋ณด์•ˆ ๋ชจ๋ธ์— ๋Œ€ํ•œ ๊ด‘๋ฒ”์œ„ํ•œ ํ† ๋ก ์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ HTTP ์ „์šฉ ์ฟ ํ‚ค ๋ฐ CSRF ํ† ํฐ์— ๋Œ€ํ•œ ์š”์ (์„ธ์…˜ ํ† ํฐ์šฉ JWT ๋ฐ/๋˜๋Š” Web Storage API ์‚ฌ์šฉ์— ๋Œ€ํ•ด XSS ๋ฐ CSRF์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ๋ณดํ˜ธ)์— ๋Œ€ํ•ด ์–ธ๊ธ‰ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ •๋ง ์ฝ์„ ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

@iaincollins ๋ญ”๊ฐ€๋ฅผ ์—ฐ๊ฒฐ

์•ˆ๋…•ํ•˜์„ธ์š” ์—ฌ๋Ÿฌ๋ถ„ :)

์งˆ๋ฌธ์ด ์žˆ์Šต๋‹ˆ๋‹ค. localstorage์—์„œ jwt๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ์„ ์œ„ํ•œ ํ† ํฐ์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†๋‹ค๋Š” ๋‚ด์šฉ์„ ์ฝ์—ˆ์Šต๋‹ˆ๋‹ค.

๋ Œ๋”๋ง์„ ์œ„ํ•œ ์„œ๋ฒ„๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ๋‹ค์Œ์œผ๋กœ ๋‚ด ์‚ฌ์ดํŠธ์˜ ์ฒซ ๋ฒˆ์งธ ์š”๊ธˆ๋งŒ ์ฒญ๊ตฌ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‚ด API๋ฅผ ์œ„ํ•œ ๋˜ ๋‹ค๋ฅธ ์„œ๋ฒ„๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ์‚ฌ์šฉ์ž/ํŒจ์Šค๋ฅผ ์ˆ˜์‹ ํ•˜๊ณ  jwt๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์–ด๋–ค ๊ฒฝ์šฐ์— ์„œ๋ฒ„์—์„œ ํ† ํฐ์„ ๊ฐ€์ ธ์™€์•ผ ํ•˜๋‚˜์š”??

๋ Œ๋”๋ง ์„œ๋ฒ„(๋‹ค์Œ)์— ํ† ํฐ์ด ํ•„์š”ํ•œ ์ด์œ ๋Š” ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

ํด๋ผ์ด์–ธํŠธ๊ฐ€ api ์„œ๋ฒ„์— ํ† ํฐ์„ ๋ณด๋‚ด์ง€ ์•Š์œผ๋ฉด api๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š์œผ๋ฉฐ ์‚ฌ์šฉ์ž๋Š” ๊ฐœ์ธ ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ํ† ํฐ์„ ๋ Œ๋”๋ง ์„œ๋ฒ„์— ๋ณด๋‚ด์•ผ ํ•˜๋Š” ์ด์œ ๋ฅผ ์ดํ•ดํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค.

@kamilml

๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ๋‘ ๊ฐ€์ง€ ์˜ต์…˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ๋‹ค์Œ ์„œ๋ฒ„์— JWT๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค(์ฟ ํ‚ค๋ฅผ ํ†ตํ•ด ๊ฐ€๋Šฅ). ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Next ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋Œ€์‹ ํ•˜์—ฌ API๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ „์ฒด ์„œ๋ฒ„ ์ธก ๋ Œ๋”๋ง์ด ์ค‘์š”ํ•˜๋‹ค๋ฉด ์ด๊ฒƒ์„ ์›ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

  2. JWT๋ฅผ ํด๋ผ์ด์–ธํŠธ ๋กœ์ปฌ ์ €์žฅ์†Œ์— ์ €์žฅํ•˜๊ณ  Next ์„œ๋ฒ„์— ์•ก์„ธ์Šค ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค. ์ด ๊ฒฝ์šฐ ์„œ๋ฒ„ ์ธก API ํ˜ธ์ถœ์„ ๋ฌด์‹œํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ ์ธก ๋กœ๋“œ๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ์ „์ฒด ๋ Œ๋”๋ง์„ ์—ฐ๊ธฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์‹œ ์—ด์–ด์„œ ์ฃ„์†กํ•˜์ง€๋งŒ ์ด ์Šค๋ ˆ๋“œ์— 2์„ผํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์ดˆ๊ธฐ R&D๊ฐ€ ์ด ๋ถ„์•ผ์—์„œ ์–ด๋–ป๊ฒŒ ์ „๊ฐœ๋˜๊ณ  ์žˆ๋Š”์ง€ ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. ์ ์€ ์ฝ”๋“œ ์˜ˆ์ œ, ๋” ๋†’์€ ์ˆ˜์ค€์˜ ํ๋ฆ„.

์ฒซ์งธ, ์ปจํ…์ŠคํŠธ๋ฅผ ์œ„ํ•ด ๋Œ€๋ถ€๋ถ„์˜ ์•ฑ์€ ์ด๋ฏธ Symfony 3(PHP)๋กœ ๋นŒ๋“œ๋˜์—ˆ์œผ๋ฉฐ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๊ฒฝํ—˜์„ ์œ„ํ•ด Vue๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„๋Š” ๋ž˜ํผ ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋งํ•˜๊ณ  ์•ฑ์ด ์„ ํƒํ•˜๋„๋ก ์•ฑ ๋ฐ์ดํ„ฐ๋ฅผ __INITIAL_STATE__ ์— ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด Symfony(SEO์šฉ)์—์„œ ๋งˆ์ผ€ํŒ… ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ๊ณผ JS๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ๋ณด๋‹ค SPA ๊ฐ™์€ ๋Š๋‚Œ์„ ์ œ๊ณตํ•˜์—ฌ ๋‹ค๋ฅธ ์˜์—ญ์—์„œ SEO๋ณด๋‹ค UX/UI๋ฅผ ์„ ํƒํ•˜๋Š” ๊ฒƒ ์‚ฌ์ด์—์„œ ๊ฒฐ์ •์„ ๋‚ด๋ฆฌ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์ฃผ๋ชฉํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ๋Š” ๊ฒƒ์€ ๋ชจ๋“  ํŽ˜์ด์ง€๊ฐ€ ์ด์ง„ ๊ณต๊ฐœ/๋น„๊ณต๊ฐœ๊ฐ€ ์•„๋‹ˆ๋ผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค(์ผ๋ถ€ ์˜ˆ์—์„œ ๋ณธ ๊ฒƒ์ฒ˜๋Ÿผ). ์ผ๋ถ€ ํŽ˜์ด์ง€๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ณต๊ฐœ๋˜๋ฉฐ ์ธ์ฆ๋œ ๊ฒฝ์šฐ ๋‹ค๋ฅด๊ฒŒ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์‚ฌ์ดํŠธ์˜ ์ผ๋ถ€์— SPA๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ–ˆ์ง€๋งŒ ๋งŽ์€ ์‹ค์šฉ์ ์ธ ๋ฉด์—์„œ ๋” ๋‚˜์œ UX/UI(๋Š๋ฆฐ TTI, ์ง„ํ–‰๋ฅ  ํ‘œ์‹œ์ค„ ๋“ฑ)์˜€์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ FOUC๋ฅผ ๋„์ž…ํ•˜๊ณ  ํ…์ŠคํŠธ๋ฅผ ๋‘ ๋ฒˆ(Symfony๋ฅผ ํ†ตํ•ด ํ•œ ๋ฒˆ, JS ๊ตฌ์„ฑ ์š”์†Œ๋กœ ํ•œ ๋ฒˆ) ๋ Œ๋”๋งํ•˜์ง€ ์•Š๋Š” ํ•œ SEO ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

SSR/์œ ๋‹ˆ๋ฒ„์„ค JS ์ž…๋ ฅ...

์ œ ๊ฒฝ์šฐ์—๋Š” HttpOnly UJSSESSID ์ฟ ํ‚ค๋ฅผ ์ƒ์„ฑํ•˜์—ฌ PHPSESSID ๋…ผ๋ฆฌ๋ฅผ ๋ชจ๋ฐฉํ•˜๊ณ  ๊ฐ’์„ ์‚ฌ์šฉ์ž์˜ JWT. Symfony ์•ฑ์€ ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ดํŠธ๋ฅผ ํƒ์ƒ‰ํ•  ๋•Œ ๊ฐ ํŽ˜์ด์ง€ ์š”์ฒญ์—์„œ ์ด๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ UJS ํŽ˜์ด์ง€๋ฅผ ๋ฐฉ๋ฌธํ•˜๋ฉด ํ•ด๋‹น ์•ฑ์˜ ์„œ๋ฒ„ ์ธก์—์„œ ์š”์ฒญ์˜ ์ฟ ํ‚ค๋ฅผ ์ˆ˜์‹ ํ•ฉ๋‹ˆ๋‹ค(๋ธŒ๋ผ์šฐ์ €์˜ ๊ธฐ๋ณธ ์ œ๊ณต ๋™์ž‘์— ๋”ฐ๋ผ). ๋Š” IF UJSSESSID ์ฟ ํ‚ค๊ฐ€ ์„ค์ •๋˜๊ณ , ์•ฑ์ด ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜์žˆ๋Š” API ํ˜ธ์ถœ (์˜ˆ๋ฅผ. /api/v1/users/me ํ†ตํ•ด ํ† ํฐ์„ ์ง€๋‚˜๊ฐ€๋Š” Authentication ํ—ค๋”). ๋‚˜๋จธ์ง€ ํ˜ธ์ถœ์€ ๋™์ผํ•œ ํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ API๋ฅผ ํ†ตํ•ด ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค. Symfony ๋กœ๊ทธ์•„์›ƒ ๋ฉ”์ปค๋‹ˆ์ฆ˜์€ UJSSESSID ์ฟ ํ‚ค๋ฅผ ์ง€์›๋‹ˆ๋‹ค. ๋‹ค์Œ์— UJS ์•ฑ์ด ๋กœ๋“œ๋˜๋ฉด ์ต๋ช… ์‚ฌ์šฉ์ž ๋ชจ๋“œ์—์„œ ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. ์ฐธ๊ณ ๋กœ Symfony ๋Œ€ UJS ํŽ˜์ด์ง€ ๋ผ์šฐํŒ…์€ Apache์˜ ProxyPass ํ†ตํ•ด ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค. LocalStorage๋Š” ์‚ฌ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ตœ์ข… ๊ฒฐ๊ณผ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ํด๋ผ์ด์–ธํŠธ ์ธก JS๊ฐ€ ์žˆ๋Š” PHP ํŽ˜์ด์ง€์™€ UJS ํŽ˜์ด์ง€์— ์žˆ๋Š” ์›ํ™œํ•œ UX์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด A/B ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ  ์‚ฌ์ดํŠธ๋ฅผ ๋ฐ˜๋ณต์ ์œผ๋กœ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์‚ฌ๋žŒ์ด ์ฒ˜์Œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. :)

์ด๊ฒƒ์€ ๊ณต์ƒ PHP/UJS๋กœ ์ธํ•ด ์กฐ๊ธˆ ๋” ๋ณต์žกํ•˜์ง€๋งŒ API ๋˜๋Š” Node.js ์„œ๋ฒ„ ๋ฏธ๋“ค์›จ์–ด(์˜ˆ: Express, Adonis ๋“ฑ)๊ฐ€ ์žˆ๋Š” ์ „์ฒด UJS ์†”๋ฃจ์…˜์—์„œ ๋™์ผํ•œ ์›์น™์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. PHP ํŽ˜์ด์ง€ ์š”์ฒญ( HttpOnly ํ”Œ๋ž˜๊ทธ)์„ ํ†ตํ•ด UJSSESSID ์ฟ ํ‚ค๋ฅผ ์„ค์ •ํ•˜๋Š” ๋Œ€์‹  ์‚ฌ์šฉ์ž๊ฐ€ SPA/UJS๋ฅผ ํ†ตํ•ด ๋กœ๊ทธ์ธํ•˜๊ฒŒ ํ•˜๊ณ  ๊ฑฐ๊ธฐ์— ์ฟ ํ‚ค๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€ ๋ง์•„์•ผ ํ•  ์ผ์€ ์•ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ JWT๋ฅผ ๋””์ฝ”๋”ฉํ•˜๊ฑฐ๋‚˜ client_secret ๊ฐ€ ํ•„์š”ํ•œ ํƒ€์‚ฌ ํ˜ธ์ถœ์„ ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ์„œ๋ฒ„์— ์œ ์ง€๋˜๋Š” ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค.

๊ทธ๊ฒƒ์ด ๋ˆ„๊ตฐ๊ฐ€๋ฅผ ๋•๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค. ๋‚ด๊ฐ€ ๋ณธ ๋‹ค๋ฅธ ์˜ˆ๋Š” ๋‚˜์—๊ฒŒ ๋„ˆ๋ฌด ์ž‘์€ ํŽ˜ํŠธ๋ฆฌ ์ ‘์‹œ์˜€์Šต๋‹ˆ๋‹ค.

@jaredpalmer ์•ˆ๋…•ํ•˜์„ธ์š”, ๊ทธ ๊ตฌํ˜„์— ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ์‹œ๋„ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ ๋ณต์‚ฌํ•˜์—ฌ ๋ถ™์—ฌ๋„ฃ๊ธฐํ•˜์—ฌ ๋Œ€์‹œ๋ณด๋“œ.js๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‚ด index.js๋กœ ๋Œ€์ฒดํ–ˆ์Šต๋‹ˆ๋‹ค.

const index = () =>
  <div>
    <span>WoooHoooo</span>
  </div>

export default withAuth(index)

withAuth hoc์—์„œ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•˜๋„๋ก ๋ณ€๊ฒฝํ–ˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜๋˜๊ธฐ ์ „์— ์ธ๋ฑ์Šค ํŽ˜์ด์ง€์˜ ๋‚ด์šฉ์ด ์—ฌ์ „ํžˆ ์ž ์‹œ ๊นœ๋ฐ•์ž…๋‹ˆ๋‹ค. :NS

์ด ๋ฌธ์ œ์˜ ์ƒํƒœ๋Š” ๋ฌด์—‡์ž…๋‹ˆ๊นŒ? ๐Ÿ˜‡

์ด๊ฒƒ์€ ์ „์ฒด ํ† ๋ก ์„ ์ฝ๋Š” ์ƒˆ๋กœ์šด ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ๋‹ค์†Œ ์••๋„๋ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ ์•„์ฃผ ๊ธฐ๋ณธ์ ์ธ ์ธ์ฆ์„ ๊ตฌํ˜„ํ•˜๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹จ 2ํŽ˜์ด์ง€(์ƒ‰์ธ, ๋กœ๊ทธ์ธ) ๋ฐ ์‚ฌ์šฉ์ž ์ •์˜ ์„œ๋ฒ„
https://github.com/trandainhan/next.js-example-authentication-with-jwt

๊ธฐ๋ณธ์ ์œผ๋กœ ์„œ๋ฒ„์— ์ธ์ฆ ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ์žˆ์–ด์„œ ๋ชจ๋“  ์š”์ฒญ์˜ ํ—ค๋”์—์„œ ํ† ํฐ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. jwt-token์€ ์ฟ ํ‚ค์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ๋‚˜๋Š” ์ด๊ฒƒ์ด ๋งค์šฐ ๊ฐ„๋‹จํ•˜๊ณ  ์ง๊ด€์ ์ด๋ฉฐ ๋งค์šฐ ์ž˜ ์ž‘๋™ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์•˜์Šต๋‹ˆ๋‹ค.

@trandainhan CSRF ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋น„๋ฐ€ ํ† ํฐ์„ ์‚ฌ์šฉํ•˜๋Š” POST ๋์ ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

@sbking CSRF ๊ณต๊ฒฉ์œผ๋กœ ๋ณดํ˜ธ๋˜๋Š” ์˜ˆ์ œ ์—”๋“œํฌ์ธํŠธ๋กœ ์†Œ์Šค ์ฝ”๋“œ ์—…๋ฐ์ดํŠธ

๐Ÿ˜ฌ ์‚ฌ์šฉํ•  ์ค€๋น„๊ฐ€ ๋˜์—ˆ๋‚˜์š”?

๋ˆ„๊ตฌ๋“ ์ง€ redux-auth-wrapper๋กœ ์ธ์ฆ์„ ์‹œ๋„ ํ–ˆ์Šต๋‹ˆ๊นŒ?

๋ชจ๋‘๋“ค ์•ˆ๋…•! ์ง€๋‚œ ๋ช‡ ๊ฐœ์›” ๋™์•ˆ ๋‚ด๊ฐ€ ๋งŒ๋“ค๊ณ  ์ด์ œ ์„ธ๋ จ๋˜๊ฒŒ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

  • ooth๋ผ๋Š” node.js์šฉ ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž ๊ณ„์ • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ: https://github.com/nmaro/ooth
  • ์ตœ์†Œํ•œ์˜ ์ƒ์šฉ๊ตฌ์™€ ํ•จ๊ป˜ next ๋ฐ ooth๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š” ์Šคํƒ€ํ„ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ: https://github.com/nmaro/staart

ํŠน์ง•:

  • ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋“ฑ๋ก
  • ์ด๋ฉ”์ผ ๋˜๋Š” ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋กœ๊ทธ์ธ
  • ์‚ฌ์šฉ์ž ์ด๋ฆ„์„ ์„ค์ •ํ•˜๊ณ , ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ , ํ™•์ธ ์ด๋ฉ”์ผ์„ ๋‹ค์‹œ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋Š” ๊ณ„์ • ํŽ˜์ด์ง€
  • ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ถ„์‹ค/๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ํŽ˜์ด์ง€
  • ์ด๋ฉ”์ผ ํŽ˜์ด์ง€ ํ™•์ธ
  • MongoDB์— ์—ฐ๊ฒฐ๋œ ๊ธฐ๋ณธ GraphQL API(1๊ฐœ ํŒŒ์ผ, ์‰ฝ๊ฒŒ ์ œ๊ฑฐ ๊ฐ€๋Šฅ)
  • ์ตœ์†Œํ•œ์˜ ์ƒ์šฉ๊ตฌ(๊ฐ€๋Šฅํ•œ ํ•œ ๋งŽ์€ ๋…ผ๋ฆฌ๊ฐ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์บก์Šํ™”๋จ)

์—ฌ๊ธฐ์—์„œ ๋ผ์ด๋ธŒ ๋ฐ๋ชจ๋ฅผ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค: http://staart.nmr.io/

๋‚˜๋Š” ์ฃผ๋กœ ์™ธ๋ถ€ ์„œ๋น„์Šค์— ์˜์กดํ•˜์ง€ ์•Š๋Š” ๊ฐ„๋‹จํ•˜๊ณ  ์ž‘๋™ํ•˜๋Š” ๊ณ„์ • ์‹œ์Šคํ…œ์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋น ๋ฅด๊ฒŒ ์‹œ์ž‘ํ•˜๊ธฐ ์œ„ํ•ด ์‹ ์†ํ•œ ํ”„๋กœํ† ํƒ€์ดํ•‘์„ ์œ„ํ•ด ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ์— ๋งค์šฐ ๋งŒ์กฑํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ณ„์† ์‚ฌ์šฉํ•˜๊ณ  ์œ ์ง€ ๊ด€๋ฆฌํ•  ์ƒ๊ฐ์ด๋ฏ€๋กœ ํ™•๋ฆฝ๋œ ๊ณ„์ • ์‹œ์Šคํ…œ + ๋…ธ๋“œ์— ๋Œ€ํ•œ UI๊ฐ€ ์—ฌ์ „ํžˆ ๋ˆ„๋ฝ๋˜์—ˆ๋‹ค๊ณ  ์ƒ๊ฐ๋˜๋ฉด ์‹œ๋„ํ•˜์‹ญ์‹œ์˜ค.

@trandainhan ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์ •๋ง ์ข‹์€ ์˜ˆ์ด๊ณ  ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ํ›จ์”ฌ ๊ฐ„๋‹จํ•˜๋ฉฐ ๋งŽ์€ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

๋‚ด๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์‹ค์ œ ์‚ฌ์šฉ ์‚ฌ๋ก€์— ๋Œ€ํ•œ ์ต์Šคํ”„๋ ˆ์Šค ์„ธ์…˜ ๋…ผ๋ฆฌ์™€ ์—ฌ์ „ํžˆ ํ˜ธํ™˜๋˜๋Š” ๋™์‹œ์— ์ด์™€ ๊ฐ™์€ ๊ฒƒ์„ ๋Œ€์‹  ์•ˆ์ „ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด nextjs-starter์˜ ํ˜„์žฌ ๋…ผ๋ฆฌ๋ฅผ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€/๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์ƒ๊ฐํ•ด๋ณผ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ฒซ ๋กœ๊ทธ์ธ ์‹œ ๋ถ€์—ฌ๋œ ํ† ํฐ์„ ์œ ์ง€ํ•˜๊ณ  ์ถ”์ ํ•˜๊ธฐ ์œ„ํ•ด ์„œ๋ฒ„๊ฐ€ ํ•„์š”ํ•œ Google oAuth API์™€ ๊ฐ™์€ ๊ฒƒ).

๋‚˜๋Š” ๊ทธ๊ฒƒ์ด ๊ฐ€๋Šฅํ•œ์ง€ ์•„์ง ์•Œ์•„๋‚ด์ง€ ๋ชปํ–ˆ์ง€๋งŒ, ๋งŒ์•ฝ ๊ทธ๋ ‡๋‹ค๋ฉด ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ํ›จ์”ฌ ๋” ์‰ฌ์šธ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋‹ค๋ฅธ ์˜ต์…˜์„ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ์„ค๋ช…ํ•˜๊ธฐ ์œ„ํ•ด ์–ด๋”˜๊ฐ€์— ์ข‹์€ ๊ธ€์„ ์“ธ ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

@trandainhan : <Link href="/">Home</Link> ๋ฅผ login.js์— ์ถ”๊ฐ€ํ•˜๊ณ  ์ƒ์„ฑ๋œ ๋งํฌ๋ฅผ ํด๋ฆญํ•˜๋ฉด ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š๊ณ ๋„ index.js์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ์ œ์—์„œ ์ด ๋ฌธ์ œ๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ œ์•ˆํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?

@iaincollins ์—ฌ๊ธฐ์—์„œ ๋Œ€๋ถ€๋ถ„์˜ ์†”๋ฃจ์…˜์ด oauth ์„œ๋น„์Šค์— ๋Œ€ํ•ด ์ธ์ฆ๋˜๋Š” ๊ฒƒ์„ ๋ด…๋‹ˆ๋‹ค. JWT์— ์˜์กดํ•˜๋Š” API์— ๋Œ€ํ•œ ์ธ์ฆ์„ ์œ„ํ•œ ์ข‹์€ ์†”๋ฃจ์…˜์ด ์žˆ์Šต๋‹ˆ๊นŒ?

@paulwehner @trandainhan ์ด ์„œ๋ฒ„ ์ธก ์ธ์ฆ ๋ผ์šฐํŒ…๋งŒ ์ฒ˜๋ฆฌํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ทธ๋Ÿฐ ์ผ์ด ๋ฐœ์ƒํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ๊ฒƒ์ด next/Link ๊ตฌ์„ฑ ์š”์†Œ์— ์˜ํ•ด ํ›„๋“œ ์•„๋ž˜์—์„œ ์ฒ˜๋ฆฌ๋˜๊ธฐ ๋•Œ๋ฌธ์— ํด๋ผ์ด์–ธํŠธ ์ธก ๋ผ์šฐํŒ…์ด next.js์—์„œ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ์•„์ง ํ™•์‹คํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

@carlos-peru ๋‚ด๋ถ€์ ์œผ๋กœ Link๋Š” ์‹ค์ œ๋กœ next/router๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ก์— ์ƒˆ ๊ฒฝ๋กœ๋ฅผ ํ‘ธ์‹œํ•ฉ๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ์ž‘์—…์ด ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ฒ˜๋ฆฌ๋˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ ์„œ๋ฒ„ ์ธก์˜ ๋ฏธ๋“ค์›จ์–ด์— ๋Œ€ํ•ด์„œ๋Š” ์—ฌ๊ธฐ์—์„œ ํ•  ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๊นŒ์ง€๋Š” ์šฐ๋ฆฌ ์ž์‹ ์˜ Link ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ๋งŒ ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์—ˆ๊ณ  URL์„ ๋ณ€๊ฒฝํ•  ๋•Œ๋งˆ๋‹ค ๋‹ค๋ฅธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

@carlos-peru ํ•ญ์ƒ ์‚ฌ์šฉ์ž ์ง€์ • ์„œ๋ฒ„๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ผ์šฐํŒ…์„ ์™„์ „ํžˆ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@trandainhan ๊ณผ @kolpav์—๊ฒŒ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค! ์ฃผ๋ง์— ์‹œ๊ฐ„์„ ๋ณด๋‚ธ ํ›„ ๋” ์ž˜ ์ดํ•ดํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ jwt ํ† ํฐ์„ ์ฟ ํ‚ค์— ์œ ์ง€ํ•˜๋Š” HOC์— ์˜์กดํ•˜๋Š” ์†”๋ฃจ์…˜์„ ๊ตฌํ˜„ํ•˜์—ฌ ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ ๋ชจ๋‘ API๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@nmaro ๋‚˜๋Š”

๊ทธ๋ž˜์„œ. ๋‹ค์Œ ๋ชจ๋ธ์—์„œ ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๊ฐ€ Facebook ํ† ํฐ์„ ์–ป์€ ๋‹ค์Œ(์‚ฌ์šฉ์ž๊ฐ€ Facebook ๋กœ๊ทธ์ธ์„ ์ˆ˜๋ฝํ•จ) ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ฐฑ์—”๋“œ์— ํ† ํฐ์„ ๋ณด๋‚ด ์‚ฌ์šฉ์ž ํ† ํฐ์„ ํ™•์ธํ•˜๊ณ  ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค(node-js ๋ฐฑ์—”๋“œ์˜ passport-facebook-token). . ๊ทธ๋Ÿฐ ๋‹ค์Œ ํ† ํฐ์ด ์–‘ํ˜ธํ•˜๋ฉด ๋ฐฑ์—”๋“œ์—์„œ ์ƒ์„ฑ๋œ JWT๋ฅผ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

๋ฌธ์ œ? ๋‹น์‹ ์˜ ๋„๊ตฌ๋Š” ํŽ˜์ด์Šค๋ถ๊ณผ ๊ตฌ๊ธ€์—์„œ ํ‚ค๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? ๋‚˜๋Š” hello.js๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์ง€๋งŒ next.js์™€ ํ˜ธํ™˜๋˜์ง€ ์•Š์œผ๋ฉฐ ์ „ ์„ธ๊ณ„๊ฐ€ ์—ฌ๊ถŒ-ํŽ˜์ด์Šค๋ถ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. API ์„œ๋ฒ„๊ฐ€ ์›น ํด๋ผ์ด์–ธํŠธ ๋ฐ ๋ชจ๋ฐ”์ผ ์•ฑ๊ณผ ํ˜ธํ™˜๋˜์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํฐ ์‹ค์ˆ˜๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค

์ถ”์‹ : ๊ท€ํ•˜์˜ slack ์ฑ„๋„์—์„œ ์ดˆ๋Œ€์žฅ์„ ๋ฐ›์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

@hmontes here - ooth ํ”„๋กœ์ ํŠธ์˜ slack ์ฑ„๋„ ์ดˆ๋Œ€(๋…ธ๋“œ์˜ ์‚ฌ์šฉ์ž ๊ณ„์ •, ํŠนํžˆ next.js)๋Š” ์ž์œ ๋กญ๊ฒŒ ๊ฐ€์ž…ํ•˜์„ธ์š”. Ooth๋Š” ๊ท€ํ•˜์˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋Š” ์—ฌ๊ถŒ-ํŽ˜์ด์Šค๋ถ-ํ† ํฐ(์—ฌ๊ถŒ-ํŽ˜์ด์Šค๋ถ ์•„๋‹˜) ๋ฐ ์—ฌ๊ถŒ-google-id-token์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

@nmaro ๋‹น์‹ ์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ๋•๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” ์—ฌ๊ถŒ-ํŽ˜์ด์Šค๋ณต(passport-facebok)๊ณผ ์—ฌ๊ถŒ-๊ตฌ๊ธ€-ID-ํ† ํฐ(passport-google-id-token)์ด ์žˆ๋Š” Graphql๋กœ ๋ฐฑ์—”๋“œ๋„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค!!!

ํ•˜์ง€๋งŒ. next.js์—์„œ facebook/google ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ์„ ์œ„ํ•œ ํ˜ธํ™˜ ํŒจํ‚ค์ง€๋ฅผ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ?

ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ๋Š” https://github.com/nmaro/staart/blob/master/packages/staart/src/components/login-facebook.js https://github ์—์„œ ๋‚ด๊ฐ€ ์‚ฌ์šฉํ•œ ๊ฒƒ๊ณผ ์œ ์‚ฌํ•œ ํŒจํ„ด์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
( oothClient.authenticate ๋Œ€์‹  ์ธ์ฆ ๊ฒฝ๋กœ์— ๊ฒŒ์‹œ ์š”์ฒญ์„ ๋ณด๋‚ด์‹ญ์‹œ์˜ค).

@timneutkens ์ตœ์†Œํ•œ์˜ with-ooth ์˜ˆ์ œ๊ฐ€ ์žˆ๋Š” PR์„ ํ™˜์˜ํ•ฉ๋‹ˆ๊นŒ?

๊ท€ํ•˜์˜ ์˜ˆ์—์„œ @nmaro . componentWillMount ๋Œ€์‹  componentDidMount๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋Š” ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

Google ๋กœ๊ทธ์ธ์„ ์œ„ํ•œ HoC(์ œ๊ณต์ž) ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋งŒ๋“ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

componentWillMount๋Š” ํด๋ผ์ด์–ธํŠธ์—์„œ๋งŒ ํ˜ธ์ถœ๋ฉ๋‹ˆ๊นŒ? ๊ทธ๋Ÿฌ๋ฉด ๊ดœ์ฐฎ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์Šน์ธ. ๊ท€ํ•˜์˜ ์˜ˆ๋ฅผ ๋“ค์–ด ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ์†Œ์…œ ๋ฏธ๋””์–ด๋กœ ์ธ์ฆ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰ ์งˆ๋ฌธ. access_token๊ณผ refresh_token์„ ๋งŒ๋“ค๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด localStorage์— ๋„ฃ๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ ๊ณ ์น  ๋•Œ ๋กœ๊ทธ์ธ์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์ด๊ฒƒ์„ next.js์— ์–ด๋””์— ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? create-react-app์—์„œ index.js์— ๋„ฃ์—ˆ์Šต๋‹ˆ๋‹ค.

...
import { loginUser } from './actions'
...
let accessToken = localStorage.getItem('access_token')
let refreshToken = localStorage.getItem('refresh_token')

if (accessToken && refreshToken) store.dispatch(loginUser({accessToken, refreshToken}))

ReactDOM.render(
  <ApolloProvider store={store} client={client}>
....

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค

ooth๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Facebook ํ† ํฐ์„ ์ €์žฅํ•˜์ง€ ์•Š๊ณ  ์—ฌ๊ถŒ์œผ๋กœ ํ•œ ๋ฒˆ๋งŒ ์‚ฌ์šฉํ•œ ๋‹ค์Œ ์ผ๋ฐ˜ ์ฟ ํ‚ค ๊ธฐ๋ฐ˜ ์‚ฌ์šฉ์ž ์„ธ์…˜์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

์•„. ์Šน์ธ. ์„ธ์…˜ ๋Œ€์‹  JWT๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋ฐฑ์—”๋“œ์— access_token์„ ๋ณด๋‚ด๊ณ  ํ† ํฐ์ด ์„œ๋น„์Šค(google, facebook)์—์„œ ์œ ํšจํ•œ์ง€ ํ™•์ธํ•œ ๋‹ค์Œ ์‚ฌ์šฉ์ž๊ฐ€ ๋‚ด ์•ฑ์— ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  JWT๋ฅผ ๋‹ค์‹œ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

๊ณ ๋ง™์Šต๋‹ˆ๋‹ค :)

JWT๋ฅผ localstorage์— ์ €์žฅํ•˜๋Š” ๊ฒƒ์€ ์œ„ํ—˜ํ•ฉ๋‹ˆ๋‹ค(XSS ๋•Œ๋ฌธ์—). ๋ชจ๋‘ ํ•˜๋‚˜์˜ ์„œ๋ฒ„/๋„๋ฉ”์ธ์— ์žˆ๋Š” ๊ฒฝ์šฐ ์ฟ ํ‚ค๋กœ ๋ณด๋‚ด๋Š” ๊ฒƒ์ด ๋” ์ข‹์œผ๋ฏ€๋กœ ํด๋ผ์ด์–ธํŠธ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ๋Š” JWT๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์ง€๋งŒ ๋ธŒ๋ผ์šฐ์ €๋Š” JWT๋Š” ์ž๋™์œผ๋กœ ์ฟ ํ‚ค๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. JWT๋Š” ๊นŒ๋‹ค๋กญ์Šต๋‹ˆ๋‹ค(์œ„์˜ ๊ธด ๋…ผ์˜ ์ฐธ์กฐ). ๊ทธ๋ž˜์„œ ooth์—์„œ ์„ธ์…˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

ooth๊ฐ€ ์™ธ๋ถ€ ์ธ์ฆ ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค๋กœ ์‚ฌ์šฉ๋˜๋Š” ๊ฒฝ์šฐ์—๋งŒ JWT๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค(์ฟ ํ‚ค๋Š” ๋™์ผํ•œ ๋„๋ฉ”์ธ์—์„œ๋งŒ ์ž‘๋™ํ•˜๊ธฐ ๋•Œ๋ฌธ์—). ๊ทธ๋Ÿฐ ๋‹ค์Œ์—๋„ ์ •ํ™•ํžˆ ํ•œ ๋ฒˆ ์‚ฌ์šฉํ•˜์—ฌ ์„œ๋ฒ„์™€์˜ ์„ธ์…˜์„ ์ƒ์„ฑํ•˜๋ฏ€๋กœ ํด๋ผ์ด์–ธํŠธ์˜ ์–ด๋Š ๊ณณ์—๋„ ์ €์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. .

JWT ๋ณด์•ˆ ์ฆ๊ฐ€๋ฅผ ์œ„ํ•ด ์ƒˆ๋กœ ๊ณ ์นจ ํ† ํฐ(oauth2์—์„œ ๊ฐ€์ ธ์˜ด)์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ชจ๋ฐ”์ผ ์•ฑ์ด ์ฟ ํ‚ค๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

@nmaro ์˜ค์ผ€์ด. ๋‹น์‹ ์„ ์ดํ•ดํ•ฉ๋‹ˆ๋‹ค. ๋‚ด ์‹ค์ˆ˜์— ๋Œ€ํ•ด ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค.

JWT ํ† ํฐ์„ ์ฟ ํ‚ค์— ์ €์žฅํ•˜๋Š” ํŠœํ† ๋ฆฌ์–ผ์ด๋‚˜ ์ฝ”๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ? ๋‚˜๋Š” https://github.com/zeit/next.js/blob/master/examples/with-firebase-authentication์„ ์ฐพ๊ณ 

์•„๋‹ˆ์š”, ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ์ ์ด ์—†์Šต๋‹ˆ๋‹ค.

@hmontes @nmaro
๋‚˜๋Š” ์ด๊ฒƒ์„ ๋‚˜ ์ž์‹ ์„ ์œ„ํ•ด ์ผ์ง€๋งŒ ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
https://github.com/malixsys/mobazoo

์•ˆ๋…•ํ•˜์„ธ์š” @malixsys ๊ณต์œ 

์•„ ์•„๋‹ˆ์˜ค, ๋™์ผํ•œ ํ”„๋กœ์„ธ์Šค์—์„œ /auth/signin์„ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ข‹์•„, ๊ทธ๋Ÿฌ๋ฉด ๊ธฐ๋ณธ์ ์œผ๋กœ ๋‚ด๊ฐ€ next.js / staart์™€ ํ•จ๊ป˜ ooth์— ์‚ฌ์šฉํ•œ ๊ฒƒ๊ณผ ๋™์ผํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

์‚ฌ์‹ค ์•„๋‹ˆ์˜ค, ์ €๋Š” ์—ฌ์ „ํžˆ ํ˜ผ๋ž€์Šค๋Ÿฝ์Šต๋‹ˆ๋‹ค. /auth/signin์—์„œ JWT๋ฅผ ๋˜๋Œ๋ ค์ฃผ์ง€๋งŒ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ๋Š” ์•„๋ฌด ๊ฒƒ๋„ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‚˜์ค‘์— "getUserFromCookie"ํ•˜์ง€๋งŒ ์ฟ ํ‚ค๋ฅผ ์„ค์ •ํ•œ ์œ„์น˜๊ฐ€ ํ‘œ์‹œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

@nmaro ์ฒ˜์Œ์—๋Š” ์œ ๋‹ˆ๋ฒ„์„ค๊ณผ ํ•จ๊ป˜ ์ž‘๋™ํ•˜๋Š” ๊ธฐ๋ณธ ๋กœ๊ทธ์ธ์„ ์›ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ์€ jwt์™€ ์ฟ ํ‚ค๋ฅผ ๋ชจ๋‘ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฟ ํ‚ค๋Š” ์œ ๋‹ˆ๋ฒ„์„ค์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด ์ €์žฅ์†Œ์˜ ๋‹ค๋ฅธ axios ํ˜ธ์ถœ์—์„œ ์•„์ง jwt๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. API_BASE_URL์ด ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์„ ๊ฐ€๋ฆฌํ‚ค๋Š” ๊ฐœ์ธ ํฌํฌ์—์„œ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
PWA์™€ ํ•จ๊ป˜ ๋‚ด TODO ๋ชฉ๋ก์— ์žˆ์Šต๋‹ˆ๋‹ค.
๋ฌธ์ œ๋ฅผ ์—ด๊ฑฐ๋‚˜ ์—ฌ๊ธฐ์—์„œ ์ €์—๊ฒŒ ํ•‘์„ ๋ณด๋‚ด์ฃผ์‹ญ์‹œ์˜ค ...

@nmaro ์ฟ ํ‚ค๋Š” ์—ฌ๊ธฐ์—์„œ saveUser() ๋ฉ๋‹ˆ๋‹ค: https://github.com/malixsys/mobazoo/blob/master/utils/auth.js

์ด ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜์—ˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ณ  ์žˆ์ง€๋งŒ ๋‚ด ์†”๋ฃจ์…˜์„ ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.
https://next-auth.now.sh/
zeit.co์˜ ์›น์‚ฌ์ดํŠธ์™€ ์•ฝ๊ฐ„ ๋น„์Šทํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

4์›” 9์ผ

๋‚˜๋Š” ๊ทธ๊ฒƒ์— ๋Œ€ํ•ด ์•ฝ๊ฐ„์˜ ์ž‘์—…์„ ์™„๋ฃŒํ–ˆ์œผ๋ฉฐ ํ˜„์žฌ ๋‹ค๋ฅธ ๊ฒƒ๋“ค๋กœ ๊ฐ€๋“ ์ฐจ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜ฅ ์–ด์จŒ๋“  ์ด๊ฒƒ์€ ๋‹ค์Œ์˜ ์šฐ์„  ์ˆœ์œ„ ๋ชฉ๋ก์—์„œ ๊ฝค ๋†’์Šต๋‹ˆ๋‹ค.

9์›” 22์ผ #2974

๊ณง ๊ณต์‹ ์ธ์ฆ ์˜ˆ์ œ๋ฅผ ๋ฆด๋ฆฌ์Šคํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์„ ๋ณ„๋„์˜ ์ €์žฅ์†Œ๋กœ ๋ฆด๋ฆฌ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? ํƒ€์•™ํฌ์Šค!

@timneutkens

์•ˆ๋…•ํ•˜์„ธ์š”, ๋‹ค์‹œ ํ•œ ๋ฒˆ ๋ฒˆ๊ฑฐ๋กญ๊ฒŒ ํ•ด์„œ ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค๋งŒ ๊ณต์‹ ์ธ์ฆ ์˜ˆ์‹œ์˜ ์ƒํƒœ๊ฐ€ ์–ด๋–ค์ง€ ๊ณต์œ ํ•ด ์ฃผ์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? ํ™•์‹คํžˆ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค๋„ ์ด ๋ฌธ์ œ์— ๋Œ€ํ•œ ๋Œ“๊ธ€ ์ˆ˜๋กœ ๋ณด๊ณ  ํŒ๋‹จํ•˜๊ณ  ์‹ถ์€ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์šฐ์„  ์ˆœ์œ„ ๋ชฉ๋ก์—์„œ ์–ผ๋งˆ๋‚˜ ๋†’์€ ์ˆœ์œ„์— ์žˆ์œผ๋ฉฐ ํฌ๋ง์„ ํ‚ค์›Œ์•ผ ํ• ๊นŒ์š”? ๐Ÿ˜„

์•ˆ๋…•ํ•˜์„ธ์š” @kolpav , ๊ทธ๋“ค์€ ๊ณต์‹์ ์œผ๋กœ ์ด๊ฒƒ์„ Next.js 5์˜ ๋กœ๋“œ๋งต์— ๋ฐœํ‘œํ–ˆ์Šต๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ ์‚ฌ์šฉ์ž ์ธ์ฆ๊ณผ ๊ฐ™์€ ์š”์ฒญ์ด ๋งŽ์€ ์˜ˆ์ œ, Next.js ๋‚ด๋ถ€ ๋ฌธ์„œ ๊ฐœ์„ , ์†Œ๊ทœ๋ชจ ๊ธฐ๋Šฅ ๋ฐ ๋ฒ„๊ทธ ์ˆ˜์ •์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

https://zeit.co/blog/next-canary#the - ๋กœ๋“œ๋งต

@babenzele ์ข‹์€ ์†Œ์‹์ž…๋‹ˆ๋‹ค. ๋†“์ณค๋‚˜๋ด์š” ใ… ใ… 

Next.js 5 ๊ฐœ๋ฐœ์— ๋Œ€ํ•œ ์ตœ์‹  ์ •๋ณด๋ฅผ ์–ด๋””์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? ํ˜„์žฌ auth0์„ ํ†ตํ•ฉํ•˜๊ณ  ์žˆ์ง€๋งŒ ๋”ฐ๋ผ์•ผ ํ•  ๊ณต์‹ nextjs ์ธ์ฆ ๊ฒฝ๋กœ/์˜ˆ๊ฐ€ ์žˆ์œผ๋ฉด ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Next.js๋Š” JWT/cookies/localStorage(๋˜๋Š” XSS/CSRF๋กœ๋ถ€ํ„ฐ ์•ˆ์ „ํ•˜๊ณ  ๋ณดํ˜ธ๋˜๋Š” ํ•œ ์กฐํ•ฉ)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ณต์‹ ๋กœ์ปฌ ์ธ์ฆ ์˜ˆ์ œ๊ฐ€ ์ ˆ์‹คํžˆ ํ•„์š”ํ•œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค... Passport.js ๋กœ์ปฌ ์ „๋žต์ด ์žˆ๋Š” ๋ณ„๋„์˜ Express API ์„œ๋ฒ„๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฅผ ์ƒ๊ฐํ•ด ๋ƒ…๋‹ˆ๋‹ค. ๋‚˜๋Š” stateless ์š”์ฒญ์— ๋Œ€ํ•ด ์ฟ ํ‚ค/localStorage์—์„œ JWT๋ฅผ ์‹œ๋„ํ–ˆ๊ณ , JWT์™€ stateless๋ฅผ ๋งˆ์นจ๋‚ด ํฌ๊ธฐํ•œ ํ›„์—๋Š” express-session ๋ฏธ๋“ค์›จ์–ด์˜ ์„ธ์…˜ ID๊ฐ€ ์žˆ๋Š” ์ผ๋ฐ˜ ์ฟ ํ‚ค๋„ ์‹œ๋„ํ–ˆ์Šต๋‹ˆ๋‹ค. Express-session์ด ์ž‘๋™ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ธํ•ด Express API ์„œ๋ฒ„์—์„œ ์ถ”๊ฐ€ ์„ธ์…˜์ด ์ƒ์„ฑ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค(saveUninitialized: false๋กœ ์‹œ๋„). ๋‚ด Next.js ์•ฑ์—์„œ Express ์ฝ”๋“œ๋ฅผ server.js๋กœ ์˜ฎ๊ธฐ๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ–ˆ์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” ๋ณ„๋„์˜ ์„œ๋ฒ„๊ฐ€ ๋” ์ข‹์•˜์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ๋‚ด ๊ตฌํ˜„์ด XSS/CSRF ๋˜๋Š” ์ฟ ํ‚ค ํ•˜์ด์žฌํ‚น์œผ๋กœ๋ถ€ํ„ฐ ์•ˆ์ „ํ•˜์ง€ ์•Š๋‹ค๊ณ  ํ™•์‹ ํ•ฉ๋‹ˆ๋‹ค. ๋กœ์ปฌ ์ธ์ฆ/๋กœ๊ทธ์ธ์— ๋Œ€ํ•œ ๋ชจ๋ฒ” ์‚ฌ๋ก€๋ฅผ ๋‹ค๋ฃจ๋Š” ๊ณต์‹ ์˜ˆ์ œ ๋˜๋Š” ๋ณต์žก์„ฑ์„ ์ฒ˜๋ฆฌํ•  Next.js์˜ ์ผ๋ถ€์ธ ๊ณต์‹ ๋ชจ๋“ˆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค!

Next.js 5.x์™€ ์ถ”๊ฐ€ ์˜ˆ์ œ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ https://nextjs-starter.now.sh ๊ฐ€ ์—ฌ์ „ํžˆ ํ™œ๋ฐœํ•˜๊ฒŒ ์œ ์ง€ ๊ด€๋ฆฌ๋˜๊ณ  ์žˆ์œผ๋ฉฐ Express, Express Sessions, CSRF( CRSF ํ† ํฐ), XSS(์„ธ์…˜ ํ† ํฐ์šฉ HTTP ์ „์šฉ ์ฟ ํ‚ค) ๋ฐ Passport JS๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ oAuth ๋ฐ ์ด๋ฉ”์ผ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

์ €๋Š” ์‹ค์ œ๋กœ ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ๋ชจ๋“ˆ๋กœ ๋ฆฌํŒฉํ† ๋งํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์šด ๋ฐฉ๋ฒ•์œผ๋กœ Next.js ํ”„๋กœ์ ํŠธ์— ์ธ์ฆ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ์ •๋ง ์‰ฝ๊ฒŒ ๋งŒ๋“œ๋Š” ๊ณผ์ •์— ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜๋ฉด ์Šคํƒ€ํ„ฐ ํ”„๋กœ์ ํŠธ ์˜ˆ์ œ์—์„œ ๋งŽ์€ ์ฝ”๋“œ๋ฅผ ๋ณต์‚ฌํ•  ํ•„์š” ์—†์ด ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ํ•จ๊ป˜ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ ์ฃผ์— ๋๋‚ผ ์ˆ˜ ์žˆ๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

์ฐธ๊ณ ๋กœ "Double Submit Cookie" ๋ฐฉ๋ฒ•์€ ๊ฐ„๋‹จํ•˜์ง€๋งŒ ์•ˆ์ „ํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์ฐพ๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ CSRF ๋ณดํ˜ธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ์‰ฌ์šด ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

localStorage์—์„œ JWT์˜ ๋ฌธ์ œ๋Š” ํด๋ผ์ด์–ธํŠธ ์ธก JavaScript์—์„œ ํ•ญ์ƒ ์ฝ์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” ์ฝ˜ํ…์ธ (์˜ˆ: ์‚ฌ์šฉ์ž๊ฐ€ ์ œ์ถœํ•œ ์ฝ˜ํ…์ธ  ๋˜๋Š” ๊ด‘๊ณ )๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ XSS๋ฅผ ํ†ตํ•œ ์„ธ์…˜ ํ•˜์ด์žฌํ‚น์„ ์œ„ํ•œ ๋ฒกํ„ฐ์ž…๋‹ˆ๋‹ค.

์„ธ์…˜ ํ† ํฐ์— HTTP ์ „์šฉ ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ HTTP ์ „์šฉ์— ํ† ํฐ ๊ฐ’์ด ์žˆ๋Š” JWT ์™€ ๊ฐ™์€ ๊ฒƒ(๋˜๋Š” ์ „์ฒด JWT๋ฅผ ์•”ํ˜ธํ™”ํ•˜๊ณ  ์„œ๋ฒ„์—์„œ HTTP ์ „์šฉ ํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ์•”ํ˜ธ ํ•ด๋…)ํ•˜๋ฉด ์„ธ์…˜์ด ์ตœ๋Œ€ํ•œ ๋ณดํ˜ธ๋ฉ๋‹ˆ๋‹ค. ์ด๋‹ค. ์ด์ƒ์ ์œผ๋กœ๋Š” ์„ธ์…˜ ํ† ํฐ์ด ํŠนํžˆ ํด๋ผ์ด์–ธํŠธ ์ธก JavaScript๋ฅผ ํ†ตํ•ด ์ฝ์„ ์ˆ˜ ์—†์–ด์•ผ ํ•œ๋‹ค๋Š” ์•„์ด๋””์–ด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฌผ๋ก  ๋งŽ์€ ์‚ฌ์ดํŠธ์—์„œ HTTP ์ „์šฉ ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์ด์œ ๋Š” ๋‹จ์ผ ํŽ˜์ด์ง€ ์•ฑ์— ๋ฌธ์ œ๊ฐ€ ๋˜๊ณ  ํ•ญ์ƒ ํ”„๋ŸฐํŠธ ์—”๋“œ์— ์ผ๋ถ€ ์ธ์ฆ ๋…ผ๋ฆฌ๊ฐ€ ํฌํ•จ๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์—ฌ์ „ํžˆ ์ด์ƒ์ ์ธ ์ ‘๊ทผ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

๋˜ ๋‹ค๋ฅธ ์˜ต์…˜์€ ์—ฌ์ „ํžˆ โ€‹โ€‹ํ™œ๋ฐœํžˆ ์œ ์ง€ ๊ด€๋ฆฌ๋˜๋Š” https://github.com/nmaro/ooth ์ด๋ฉฐ, ์ด๋ฏธ ํŒจํ‚ค์ง€๋กœ ์ œ๊ณต๋˜๋ฉฐ ๋ช‡ ๊ฐ€์ง€ ํ”„๋กœ๋•์…˜ ์•ฑ์—์„œ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

@iaincollins Next.js ์Šคํƒ€ํ„ฐ ํ”„๋กœ์ ํŠธ๋ฅผ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ์‚ดํŽด๋ณด๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ค‘์š”ํ•œ ๋ณด์•ˆ ๊ด€๋ จ(XSS/CSRF) ๋ถ€๋ถ„์„ ๋ถ„๋ฆฌํ•˜์—ฌ ๋‚ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ํ†ตํ•ฉํ•˜๊ฑฐ๋‚˜ ๋ณ„๋„์˜ ๋ชจ๋“ˆ์„ ์™„๋ฃŒํ•  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ๋ชจ๋“ˆ์˜ ๊ฐœ๋ฐœ์„ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ณณ์ด ์žˆ์Šต๋‹ˆ๊นŒ?

์•ˆ๋…•ํ•˜์„ธ์š” @kelleg1 ์ž…๋‹ˆ๋‹ค .

๋ณ„๋„์˜ ๋ชจ๋“ˆ์€ ์ด์ œ ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ์—์„œ ๋” ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก next-auth ๋ชจ๋“ˆ๋กœ ๊ฒŒ์‹œ๋ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—๋Š” ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ฃผ๋Š” ์˜ˆ์ œ ํ”„๋กœ์ ํŠธ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ถ”๊ฐ€ ์ฐธ์กฐ๋ฅผ ์œ„ํ•ด nextjs-starter.now.sh ํ”„๋กœ์ ํŠธ๋Š” ์ด์ œ ์‹œ์ž‘ ํ”„๋กœ์ ํŠธ์˜ ์ฝ”๋“œ๋ฅผ ํฌ๊ฒŒ ๋‹จ์ˆœํ™”ํ•˜๋Š” next-auth๋„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด์ œ ์ƒˆ๋กœ์šด oAuth ์ œ๊ณต์ž์— ๋Œ€ํ•œ ์ง€์›์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค( ์˜ˆ์ œ๋Š” ์—ฌ์ „ํžˆ Mongo DB๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ).

๊ทธ๋ž˜๋„ ์—ฌ์ „ํžˆ ๋‹ค์†Œ ๋ณต์žกํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ์กด ์•ฑ์ด ์žˆ์œผ๋ฉด ์ฐธ์กฐ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์‰ฌ์šธ ์ˆ˜ ์žˆ์ง€๋งŒ ๊ทธ๋ ‡๋‹ค๋ฉด ๋„์›€์ด๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

์ฃผ์˜: CSRF๋Š” ํ˜„์žฌ ์—ฌ์ „ํžˆ ๋งค์šฐ ๋ฐ€์ ‘ํ•˜๊ฒŒ ๊ฒฐํ•ฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. lusca๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ res.locals._csrf๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์ง€๋งŒ ๋‹ค๋ฅธ CSRF ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋‹ค๋ฅธ ๊ฐœ์ธ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋‚˜๋Š” ๊ทธ๊ฒƒ์ด ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ์›ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ์‚ฌ์šฉํ•˜๊ธฐ๊ฐ€ ์—ฌ์ „ํžˆ ๋” ๋ณต์žกํ•˜๋‹ค๋Š” ์ ์— ๊ฐ์‚ฌํ•˜์ง€๋งŒ ์ ์–ด๋„ ์ง€๊ธˆ์€ ์ธ์ฆ ์ฝ”๋“œ๊ฐ€ ๋งˆ์นจ๋‚ด ๋ชจ๋“ˆ๋กœ ๋ถ„๋ฆฌ๋˜์–ด ๋ฆฌํŒฉํ† ๋ง์„ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‹œ๊ฐ„์ด ์ง€๋‚จ์— ๋”ฐ๋ผ (๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋Œ€ํ•œ ๊ธฐ๋ณธ ํ•ธ๋“ค๋Ÿฌ์™€ ๋” ์‰ฌ์šด oAuth ๊ตฌ์„ฑ์œผ๋กœ) ์‚ฌ์šฉํ•˜๊ธฐ๊ฐ€ ๋” ์‰ฌ์›Œ์ง€๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

@iaincollins next.js์— ๋Œ€ํ•œ ์œ ์ผํ•œ ์ข…์†์„ฑ์€ https://github.com/iaincollins/next-auth/blob/master/index.js#L342 ์ž…๋‹ˆ๊นŒ? ๊ทธ๋ ‡๋‹ค๋ฉด lib next.js๋ฅผ ๋ถˆ๊ฐ€์ง€๋ก ์ ์œผ๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

@seduboi ์ „์ ์œผ๋กœ ๋™์˜ํ•ฉ๋‹ˆ๋‹ค!

์—ฌ๊ธฐ๋ณด๋‹ค๋Š” GitHub repo ๋ฌธ์ œ์— ๋Œ€ํ•œ ์ข‹์€ ํ† ๋ก ์ด ๋  ์ˆ˜ ์žˆ์ง€๋งŒ. ๐Ÿ™‚ ๊ฐœ์„ ํ•˜๊ณ  ๋‹จ์ˆœํ™”ํ•˜๊ณ  ๋ณด๋‹ค ์ผ๋ฐ˜์ ์œผ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์ œ์•ˆํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ํ˜‘๋ ฅํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

(๊ฐ€๋Šฅํ•œ ํ•œ ๋‹ค์Œ๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์šด ๊ฒƒ์„ ๊ฐ–๋Š” ๊ฒƒ์ด ์—ฌ์ „ํžˆ ์ฃผ์š” ๋ชฉํ‘œ์ด์ง€๋งŒ, ๊ทธ๊ฒƒ์ด ๋‹ค์Œ ํŠน์ • ์ดˆ์  ์˜ต์…˜์œผ๋กœ ๋๋‚˜๋”๋ผ๋„ ๋ฐฐํƒ€์ ์ผ ํ•„์š”๋Š” ์—†๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.)

@timneutkens nextjs 5 ์ถœ์‹œ๋ฅผ ์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ€๊นŒ์šด ์žฅ๋ž˜์— examples ํด๋”์— ๊ณต์‹ ๋กœ์ปฌ ์ธ์ฆ ์˜ˆ์ œ๊ฐ€ ์ถ”๊ฐ€๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์„๊นŒ์š”? ์•„๋‹ˆ๋ฉด ์ฐจํ›„ ๋ฆด๋ฆฌ์Šค๋ฅผ ์œ„ํ•ด ์•„์ง ์ž‘์—… ์ค‘์ธ ๊ฒƒ์ž…๋‹ˆ๊นŒ?

@jaredpalmer ๊ท€ํ•˜์˜ ์ ‘๊ทผ ๋ฐฉ์‹์ด ๋งˆ์Œ์— getUser ๋ฉ”์„œ๋“œ๋Š” ์•ˆ์ „ํ•ฉ๋‹ˆ๊นŒ? ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ localstorage ๋‚ด์—์„œ ์†์œผ๋กœ ๋ฌธ์ž์—ด์„ ๋ณ€๊ฒฝํ•˜๋ฉด ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์ด ์ด์— ์˜์กดํ•˜๋Š” ๊ฒƒ์ด ํ˜„๋ช…ํ• ๊นŒ์š”? ๋งค๋ฒˆ ํ† ํฐ์˜ JWT ๊ณต๊ฐœ ๋ถ€๋ถ„์„ ๋””์ฝ”๋”ฉํ•˜๊ณ  ๊ฑฐ๊ธฐ์—์„œ ์‚ฌ์šฉ์ž ์ƒํƒœ๋ฅผ ์ฝ๋Š” ๋ฉ”์†Œ๋“œ๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์ด ๋” ํ•ฉ๋ฆฌ์ ์ž…๋‹ˆ๊นŒ? ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด ์‚ฌ๋ฌผ์˜ JWT ํŠน์„ฑ์œผ๋กœ ์ธํ•ด ์ด ์ƒํƒœ๋ฅผ ๋” ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹น์‹ ์˜ ์˜๊ฒฌ ๊ฒƒ์ž…๋‹ˆ๋‹ค?

์ฟ ํ‚ค์— ์ €์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‚˜๋Š” Next๊ฐ€ ์‚ฌ์šฉ์ž ์ •์˜ ์„œ๋ฒ„๋ฅผ ์ง€์›ํ•˜๊ธฐ ์ „์— ์ผ์Šต๋‹ˆ๋‹ค.

@jaredpalmer ๊ทธ๊ฒƒ์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ๋ ค getInitialProps ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๊นŒ?

_์ €์žฅ์— ๋Œ€ํ•ด ๋งํ•˜๋Š” ๊ฒƒ์ด ๋กœ์ปฌ ์ €์žฅ์†Œ/์›น ์ €์žฅ์†Œ์˜ ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ์ธ ๊ฒฝ์šฐ_ ์ฃผ์˜ํ•˜์‹ญ์‹œ์˜ค.

"์›น ์ €์žฅ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค. ์›น ์ €์žฅ์†Œ๋Š” ๋ณด์•ˆ ์ €์žฅ์†Œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ์œ ์„ ์„ ํ†ตํ•ด ์ „์†ก๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ฟ ํ‚ค๋ณด๋‹ค "๋” ์•ˆ์ „ํ•˜์ง€" ์•Š์Šต๋‹ˆ๋‹ค. ์•”ํ˜ธํ™”๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ณด์•ˆ ๋˜๋Š” HTTP ์ „์šฉ ํ”Œ๋ž˜๊ทธ๊ฐ€ ์—†์œผ๋ฏ€๋กœ ์ด ์„ธ์…˜์ด๋‚˜ ๊ธฐํƒ€ ๋ณด์•ˆ ํ† ํฐ์„ ๋ณด๊ด€ํ•˜๋Š” ์žฅ์†Œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค."

๋กœ๊ทธ์ธ ํ›„ ์ฟ ํ‚ค์— ํ† ํฐ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์ฟ ํ‚ค-js๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ์„œ๋ฒ„์—์„œ ์ต์Šคํ”„๋ ˆ์Šค ์ฟ ํ‚ค ํŒŒ์„œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ get req.headers.cookies.myToken ๋˜๋Š” ์ด์™€ ๋™๋“ฑํ•œ ํ•ญ๋ชฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž„์‹œ์˜ getInitialProps์—์„œ req๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•œ ๋‹ค์Œ req.cookies์—์„œ ํ† ํฐ์„ ๊ฐ€์ ธ์˜ค๊ณ  ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด Cookies.get('mytoken')์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์ด ์‹œ์ ์—์„œ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„์˜ ํ† ํฐ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ fetch/axios ๋ž˜ํผ/์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค๊ณ  getInitialProps์—์„œ next์˜ ctx์™€ ๋ณ‘ํ•ฉํ•˜์—ฌ ๋ชจ๋“  ํŽ˜์ด์ง€๊ฐ€ ์ธ์ฆ๋œ ๋™ํ˜• ์š”์ฒญ์„ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ๊ฐ–๊ธฐ๋ฅผ ์›ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์‚ฌ์šฉ์ž๋ฅผ ์ž„์‹œ๋ฐฉํŽธ์œผ๋กœ ๋Œ์–ด๋“ค์ด๊ณ  ์‹ถ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ชจ๋“  ๊ณณ์—์„œ ๋ฐ˜๋ณตํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ withUser(withTeam(Page))์™€ ๊ฐ™์€ ๊ณตํ†ต ์—”ํ„ฐํ‹ฐ์— ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋” ๋งŽ์€ ์ž„์‹œ ํ•ญ๋ชฉ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

getUser๋Š” ๋‚˜์œ ์ƒ๊ฐ์ž…๋‹ˆ๋‹ค. ํ† ํฐ๋งŒ ์ €์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

@jaredpalmer ๋‚˜๋Š” ๊ฑฐ์˜ ์ •ํ™•ํžˆ ๊ทธ์™€ ๊ฐ™์€ ์ ‘๊ทผ ๋ฐฉ์‹์„ ๊ตฌ์ถ•ํ–ˆ์œผ๋ฉฐ ๋ชจ๋“  ๊ฒƒ์ด ์ž˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์ง€๊ธˆ ํ•ด๊ฒฐํ•˜๋ ค๋Š” ๋ฌธ์ œ๋Š” ํ† ํฐ์„ ์ƒˆ๋กœ ๊ณ ์น˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ๋‚ด๊ฐ€ ์ž‘์—…ํ•˜๊ณ  ์žˆ๋Š” API์—๋Š” ์ƒ๋Œ€์ ์œผ๋กœ ์ˆ˜๋ช…์ด ์งง์€ ํ† ํฐ(2์‹œ๊ฐ„)์ด ์žˆ์œผ๋ฉฐ ์•ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ๋™์•ˆ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๋„๋ก ์ผ๋ถ€ ์‹œ์Šคํ…œ์— ๋Œ€ํ•ด ๋จธ๋ฆฌ๋ฅผ ์“ฐ๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๊ฒƒ์— ๋Œ€ํ•œ ์˜๊ฒฌ์ด ์žˆ์Šต๋‹ˆ๊นŒ?

์‚ฌ์šฉ์ž๋ฅผ ์œ„ํ•ด next.js๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜์ง€ ์•Š์Œ์œผ๋กœ์จ ๋ชจ๋“  ํŽ˜์ด์ง€ ์ „ํ™˜์— ๋Œ€ํ•œ ์š”์ฒญ์„ ์ €์žฅํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ ค๋ฉด server.js์˜ ์ฟ ํ‚ค์—์„œ ํ† ํฐ์„ ์ฝ๊ณ  ์‚ฌ์šฉ์ž๋ฅผ ๊ฐ€์ ธ์™€ ๋‹ค์Œ ์š”์ฒญ ์ฒ˜๋ฆฌ๊ธฐ์— ์ „๋‹ฌํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๋ฌธ์„œ์—์„œ props์—์„œ ๊ฐ€์ ธ์™€ window.USER์™€ ๊ฐ™์€ JSON์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ๊ท€ํ•˜์˜ ์ž„์‹œ์—์„œ๋Š” ํด๋ผ์ด์–ธํŠธ ๋žœ๋“œ์—์žˆ์„ ๋•Œ ์ฐฝ์—์„œ ์ฝ์Šต๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ, 403 ์ฝ”๋“œ๋ฅผ ์ˆ˜์‹ ํ•˜๋ฉด ์ฆ‰์‹œ ์ฟ ํ‚ค๋ฅผ ์ง€์šฐ๊ณ  ํŽ˜์ด์ง€๋ฅผ ๋‹ค์‹œ ๋กœ๋“œํ•˜๋Š” ์‘๋‹ต ์ธํ„ฐ์…‰ํ„ฐ์™€ ํ•จ๊ป˜ axios๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

@pbrandone ๋ฐ˜๋ณต์ ์ด์ง€๋งŒ ๊ฐ€์žฅ ๊ฐ„๋‹จํ•˜๊ณ  ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ์†”๋ฃจ์…˜์€ ๋ชจ๋“  ์š”์ฒญ๊ณผ ํ•จ๊ป˜ ํ† ํฐ์„ ๋ณด๋‚ด๊ณ  ์‘๋‹ต์˜ ํ—ค๋”/๋ณธ๋ฌธ์—์„œ ์ƒˆ๋กœ ์ƒˆ๋กœ ๊ณ ์นœ ํ† ํฐ์„ ๋ฐ›๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๋Š” ์š”์ฒญ/์‘๋‹ต ์ฃผ๊ธฐ๋งˆ๋‹ค ์•Œ๋ ค์ง„ ๊ฐ’์„ ์—…๋ฐ์ดํŠธํ•˜์—ฌ ๊ฒฐ์ฝ” ์˜ค๋ž˜๋˜์ง€ ์•Š๊ณ  ์˜ค์šฉ๋  ์ˆ˜ ์—†๋Š” ์ผํšŒ์šฉ ํ† ํฐ์„ ํšจ๊ณผ์ ์œผ๋กœ ๋ถ€์—ฌํ•ฉ๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ ์ด ์ ‘๊ทผ ๋ฐฉ์‹์—์„œ /token/refresh ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ดˆ๊ธฐ "๊นจ์šฐ๊ธฐ"์—๋งŒ ์‚ฌ์šฉ๋˜๋ฉฐ( <App/> ๋Œ€ํ•œ componentWillMount hook ์ƒ๊ฐ) ๋งˆ์ง€๋ง‰์œผ๋กœ ์ €์žฅ๋œ ํ† ํฐ์€ ์—ฌ์ „ํžˆ โ€‹โ€‹์œ ํšจํ•˜๊ณ  ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์ƒˆ๋กœ ๊ณ ์นจ ํ† ํฐ์„ ์ฒ˜๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†์—ˆ์ง€๋งŒ probs๋Š” axios ์ธํ„ฐ์…‰ํ„ฐ์—์„œ ์ฒ˜๋ฆฌํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๋” ์ƒ๊ฐํ•ด์•ผ ํ•˜์ง€๋งŒ ์ด๋ก ์ ์œผ๋กœ ์ž˜๋ชป๋œ ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„๊ณ  ์ƒˆ๋กœ ๊ณ ์นจ ํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ ํ† ํฐ์„ ์–ป๊ณ  ์ƒˆ ํ† ํฐ์œผ๋กœ ์ฟ ํ‚ค๋ฅผ ์„ค์ •ํ•˜๊ณ  ์ƒˆ ํ† ํฐ์œผ๋กœ ์ฒซ ๋ฒˆ์งธ ์š”์ฒญ์„ ๋‹ค์‹œ ์žฌ์ƒํ•ฉ๋‹ˆ๋‹ค. Axios ์ธํ„ฐ์…‰ํ„ฐ๋Š” ์ด๋Ÿฌํ•œ ๊ฒƒ๋“ค์„ ์ถ”์ƒํ™”ํ•˜๊ณ  ์ œํ’ˆ ์ฝ”๋“œ์—์„œ ๋ณด์ด์ง€ ์•Š๊ฒŒ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ •๋ง ๊ฐ•๋ ฅํ•ฉ๋‹ˆ๋‹ค. ์š”์ฒญ ๋ฐ ์‘๋‹ต ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•˜๊ฑฐ๋‚˜ ํ„ธ์ด ๋งŽ์•„์งˆ ์ˆ˜ ์žˆ๋Š” ์ผ์ข…์˜ ์ƒํƒœ ์ €์žฅ ์ง„ํ–‰ ์ค‘์ธ ๊ฐ์ฒด/๋งต์„ ๋ณด์œ ํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

์•„๋‹ˆ๋ฉด ๋ˆ„๊ฐ€๊ฐ€ ๋งํ•œ ๋Œ€๋กœ ํ•˜์„ธ์š”. ํ›จ์”ฌ ์‰ฝ๊ฒŒ.

ํ† ํฐ์„ ์ƒˆ๋กœ ๊ณ ์น˜๋ ค๋ฉด ์‚ฌ์šฉ์ž ์ง€์ • ์„œ๋ฒ„ ์„ค์ •์„ ํ†ตํ•ด ์ฟ ํ‚ค(์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ)๋ฅผ ๊ฐ€๋กœ์ฑ„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‚˜๋Š” ์ต์Šคํ”„๋ ˆ์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๊ฒƒ์„ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด JWT ํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฟ ํ‚ค๋ฅผ ํ†ตํ•ด ๋‚ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ํƒ์ƒ‰ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ์„œ๋ฒ„ ์ธก์—์„œ ํŽ˜์ด์ง€ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋‘ ๊ฐ€์ง€ ์‚ฌ์šฉ ์‚ฌ๋ก€๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค(๋ˆ„๊ตฐ๊ฐ€ ์ˆ˜๋™์œผ๋กœ URL์„ ์ž…๋ ฅํ•˜๊ฑฐ๋‚˜ ํŽ˜์ด์ง€ ์ƒˆ๋กœ ๊ณ ์นจ์„ ํ•  ๋•Œ๋งˆ๋‹ค). ๊ทธ๋Ÿฌ๋‚˜ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋™์ผํ•œ ํ๋ฆ„์ž…๋‹ˆ๋‹ค. ํ•˜๋‚˜๋Š” ํด๋ผ์ด์–ธํŠธ ์ธก์ด๊ณ  ๋‹ค๋ฅธ ํ•˜๋‚˜๋Š” ์„œ๋ฒ„ ์ธก์ด๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค๋ฅด๊ฒŒ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. Express๋ฅผ ์‚ฌ์šฉํ•œ ์ดํ›„๋กœ ์„œ๋ฒ„ ์ธก์—์„œ๋Š” ์ฟ ํ‚ค๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ๊ทธ ์•ˆ์— ์žˆ๋Š” JWT๋ฅผ ๋””์ฝ”๋”ฉํ•˜๊ณ  ๋งŒ๋ฃŒ๋ฅผ ํ™•์ธํ•˜๋Š” ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ๋งŒ๋ฃŒ๋œ ๊ฒฝ์šฐ ์ƒˆ ํ† ํฐ์„ ๊ฐ€์ ธ์˜ค๋„๋ก ์š”์ฒญํ•˜๊ณ  ๋””์ฝ”๋”ฉ๋œ ์‚ฌ์šฉ์ž๋ฅผ ํด๋ผ์ด์–ธํŠธ์˜ redux์— ๊ณ„์† ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ์—๋Š” ํด๋ผ์ด์–ธํŠธ์˜ ์ผ๋ถ€๋ฅผ ํƒ์ƒ‰ํ•  ๋•Œ๋งˆ๋‹ค ์‚ฌ์šฉ์ž๋ฅผ ์ง€์†์ ์œผ๋กœ ํ™•์ธํ•˜๋Š” ๋ณด์•ˆ ๋งํฌ์šฉ HOC ๋ž˜ํผ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. HOC๋Š” ์ฟ ํ‚ค ๋˜๋Š” JWT(์ œ ๊ฒฝ์šฐ์—๋Š” JWT๊ฐ€ ์ฟ ํ‚ค์— ์žˆ์Œ)๋ฅผ ๊ฐ€์ ธ์™€์„œ ์—ฌ์ „ํžˆ ์œ ํšจํ•œ์ง€ ๋น ๋ฅด๊ฒŒ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์œ ํšจํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์ƒˆ๋กœ ๊ณ ์ณ์ง„ JWT/์ฟ ํ‚ค๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ๊ณ„์† ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๋กœ๊ทธ์•„์›ƒ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ, ๋” ๋ณต์žกํ•˜์ง€๋งŒ ๋” ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค. ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค! ๊ด€์‹ฌ์ด ์žˆ์œผ์‹œ๋ฉด ์–ธ์ œ๋“ ์ง€ ์งˆ๋ฌธํ•˜์‹ญ์‹œ์˜ค. ๋ชจ๋“  ๊ฒƒ์„ ํŒŒ์•…ํ•˜๋Š” ๋ฐ ์‹œ๊ฐ„์ด ์ข€ ๊ฑธ๋ ธ์Šต๋‹ˆ๋‹ค. ๊ฐœ์ธ์ ์œผ๋กœ Github ๋˜๋Š” FB ๋“ฑ์œผ๋กœ ๋กœ๊ทธ์ธํ•˜๋Š” ์‚ฌ๋žŒ๋“ค์— ๋Œ€ํ•ด ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค. ๋งŽ์€ ๊ฒฝ์šฐ์—๋Š” ๊ดœ์ฐฎ์ง€๋งŒ ์–ด๋–ค ๊ฒฝ์šฐ์—๋Š” ์ „๋ฌธ๊ฐ€๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ๋‚˜๋Š” ์•„์ง ์€ํ–‰, ์˜๋ฃŒ, ๋‚ด๊ฐ€ ์ง€๋ถˆํ•˜๋Š” ์ฒญ๊ตฌ์„œ ๋“ฑ์„ ๋ณด์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‚ด FB ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•˜์‹ญ์‹œ์˜ค. ์‹œ๊ฐ„ ๋ฌธ์ œ์ผ ์ˆ˜ ์žˆ์ง€๋งŒ ;)

์•ก์„ธ์Šค ํ† ํฐ ์ƒˆ๋กœ ๊ณ ์นจ์— ๊ด€ํ•ด์„œ๋Š”;
์ด๊ฒƒ์ด ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ•์ธ์ง€ ํ™•์‹คํ•˜์ง€ ์•Š์ง€๋งŒ ๋‚ด ์•ก์„ธ์Šค ํ† ํฐ์€ ์ˆ˜๋ช…์ด ๋งค์šฐ ์งง์Šต๋‹ˆ๋‹ค(1๋ถ„ ์ •๋„). ํด๋ผ์ด์–ธํŠธ ์ธก ํ† ํฐ ๋งŒ๋ฃŒ ๋ฌธ์ œ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์ด ์ ‘๊ทผ ๋ฐฉ์‹์„ ์ทจํ–ˆ์Šต๋‹ˆ๋‹ค.
ํŽ˜์ด์ง€๊ฐ€ ๋กœ๋“œ๋  ๋•Œ๋งˆ๋‹ค ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ๋งŒ๋ฃŒ๋œ ๊ฒฝ์šฐ ์ƒˆ๋กœ ๊ณ ์นฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์—ฌ์ „ํžˆ ์œ ํšจํ•˜๋‹ค๋ฉด ๋งŒ๋ฃŒ๊นŒ์ง€ ๋‚จ์€ ์‹œ๊ฐ„์„ ํ™•์ธํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ๋งŒ๋ฃŒ ์‹œ ์•ก์„ธ์Šค ํ† ํฐ์„ ์ƒˆ๋กœ ๊ณ ์น˜๋Š” ํƒ€์ž„์•„์›ƒ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ๋ฐฉ๋ฌธ์ž๊ฐ€ ์‚ฌ์ดํŠธ์— ์žˆ๋Š” ๋™์•ˆ ์ด ํ”„๋กœ์„ธ์Šค๋ฅผ ๋ฐ˜๋ณตํ•ฉ๋‹ˆ๋‹ค.

callRefreshAuthenticationLater(){
    clearTimeout(this.accessTokenRefreshmentTimeout)
    this.accessTokenRefreshmentTimeout = setTimeout(() => {
      this.refreshAuthentication()
    }, this.authService.howMuchUntilExpiration(this.authService.getToken('access_token')))
  }

๋งŒ๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋ฐ€๋ฆฌ์ดˆ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

howMuchUntilExpiration(token){
    return normalizeTimestamp(jwtDecode(token).exp) - Date.now()
  }

์ด ์ ‘๊ทผ ๋ฐฉ์‹์— ๋Œ€ํ•œ ๋ชจ๋“  ํ”ผ๋“œ๋ฐฑ์€ ๋งค์šฐ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค.

@kunokdev - ์ข‹์€ ์‹œ์ž‘์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๋น„์Šทํ•œ ํ•จ์ˆ˜๋ฅผ ์˜ฎ๊ฒผ์Šต๋‹ˆ๋‹ค. ๋‚จ์€ ์‹œ๊ฐ„์„ ํ•จ์ˆ˜๊ฐ€ ๋งŒ๋ฃŒ๋˜์—ˆ๋Š”์ง€ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ๋ถ€์šธ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋กœ ์ด๋™ํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ์ข€ ๋” ์ฝ๊ธฐ ์‰ฝ๊ณ  ๊ฐ„๋‹จํ•˜๊ฒŒ ๋งŒ๋“ค์—ˆ์œผ๋ฉฐ ํƒ€์ด๋จธ ์žฌ์„ค์ •์— ๋Œ€ํ•ด ๊ฑฑ์ •ํ•  ํ•„์š”๊ฐ€ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ์ธ์ฆ์ด ํ•„์š”ํ•œ ๋ชจ๋“  ์ข…๋ฅ˜์˜ ์š”์ฒญ์— ๋Œ€ํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ๋งŒ๋ฃŒ๋˜์—ˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์นด์šดํŠธ๋‹ค์šด ํƒ€์ด๋จธ๋‚˜ ํด๋ผ์ด์–ธํŠธ ๋˜๋Š” ๋””๋ฒ„๊น…์— ํ‘œ์‹œ๋˜๋Š” ๊ฒƒ๊ณผ ์œ ์‚ฌํ•œ ๊ฒƒ์ด ์žˆ๋‹ค๋ฉด ๊ธฐ๋Šฅ์ด ์ด์ƒ์ ์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‚ด 2์„ผํŠธ ๐Ÿ‘

auth/login ์˜ˆ์ œ๊ฐ€ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ์˜ˆ์ œ๊ฐ€ ๋˜์–ด์•ผ ํ•˜์ง€ ์•Š์„๊นŒ์š”? ์ฆ‰, ๋ฉ”๋ชจ๋ฆฌ ์ €์žฅ์†Œ์™€ JWT ๋˜๋Š” ์ด์ƒํ•œ ํ† ํฐ์ด ์—†์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ "๋กœ๊ทธ์ธ"ํ•œ ์‚ฌ๋žŒ์— ๋Œ€ํ•ด ์ƒ๊ฐํ•  ๋•Œ ์ฟ ํ‚ค/ํ™œ์„ฑ ์„ธ์…˜์ด ์žˆ์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

๋กœ๊ทธ์ธ HOC ์ž‘์—… ํด๋ผ์ด์–ธํŠธ ๋ฐ ์„œ๋ฒ„ ์ธก์ด ์žˆ์Šต๋‹ˆ๋‹ค. JWT์™€ ๋ฐฑ์—”๋“œ(๋…ธ๋“œ, django ๋“ฑ ๋ฌด์—‡์ด๋“  ๊ฐ€๋Šฅ)๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๊ฒƒ์„ ํ™•์ธํ•˜๊ณ  ๋ชจ๋“  ํ”ผ๋“œ๋ฐฑ์„ ๋†’์ด ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค

https://github.com/hugotox/AppStarter

๊ด€๋ จ ์ฝ”๋“œ๋Š” ์—ฌ๊ธฐ https://github.com/hugotox/AppStarter/blob/master/src/components/auth/login-required.js

@hugotox ์ธ์ฆ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด Redux ์ €์žฅ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์•„์ด๋””์–ด๊ฐ€ ๋งˆ์Œ์— store.dispatch ์—์„œ getInitialProps store.dispatch ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœํ•˜๋ฉด ์›์น˜ ์•Š๋Š” ๋ถ€์ž‘์šฉ์— ๋Œ€๋น„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„์ง ๋๋‚˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ์—์„œ ์‚ฌ์šฉ ์˜ˆ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

export default withRedux(initStore, mapStateToProps)(
   loginRequired([PUBLIC])(MyPage)
)

์—์„œ getInitialProps ๋‹น์‹ ์ด ์ „ํ™” verifyToken ์ „์— verificationOk ๊ทธ๋ž˜์„œ, MyPage.mapStateToProps (๊ฐ€ store.auth.user์„ ์ˆ˜์‹ ํ•˜๋Š” ๊ฒฝ์šฐ) 2 ๋ฒˆ ํ˜ธ์ถœ๋˜๊ณ  ์ฒ˜์Œ store.auth.user ๋Š” ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•œ ํŽ˜์ด์ง€์˜ ๊ฒฝ์šฐ์—๋„ null์ด ๋ฉ๋‹ˆ๋‹ค.

์–ด์ œ ์ €๋Š” Next.js ๊ธฐ๋ฐ˜ ์Šคํƒ€ํ„ฐ ํ‚คํŠธ์˜ ์ฒซ ๋ฒˆ์งธ WIP ๋ฒ„์ „๋„ ์ถœ์‹œํ–ˆ์ง€๋งŒ Docker, Flow ๋ฐ fast-redux๋กœ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค: https://github.com/dogada/microchain

๋‹ค๋ฅธ ๋„๋ฉ”์ธ์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” API ์„œ๋ฒ„ ๋ฐ ์›น ์•ฑ์šฉ ์ „์šฉ Docker ์ด๋ฏธ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์‹ค์ œ ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ์„ ํ—ˆ์šฉํ•˜๊ณ  ์˜ˆ๋ฅผ ๋“ค์–ด ๋ชจ๋ฐ”์ผ ํด๋ผ์ด์–ธํŠธ์šฉ OAuth ์ „๋‹ฌ์ž ํ† ํฐ๋„ ๋ฐœํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์ž‘๋™ ์†”๋ฃจ์…˜์„ ์ฐพ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

@spencersmb ํ›Œ๋ฅญํ•œ ๊ฑด๋ฐฐ! ์ด๊ฒƒ์€ ๋‚ด๊ฐ€ ๊ทธ๊ฒƒ์— ๋Œ€ํ•ด ์ง„ํ–‰ํ–ˆ๋˜ ๋ฐฉ์‹๊ณผ ๊ฑฐ์˜ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค. ์ฒ˜์Œ ๋ฐฉ๋ฌธ(๋กœ๊ทธ์ธ ์•ˆํ•จ)ํ•  ๋•Œ URL์— ๋Œ€ํ•œ ์„œ๋ฒ„ ์ธก ๋ Œ๋”๋ง ์ธก๋ฉด์—์„œ ์ฟ ํ‚ค๊ฐ€ ์—†๊ณ  ์˜ˆ๋ฅผ ๋“ค์–ด ํŽ˜์ด์ง€/login.js ์œ ํ˜• ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜๋˜๋Š” ๊ฒƒ์„ ๋ณด๊ณ  ๊ฐ€๋กœ์ฑ„๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ?

@james-ff ํ—ˆ๊ณต์— ์†Œ๋ฆฌ์ง€๋ฅด๋Š” ๊ฒƒ ๊ฐ™๋”๋ผ๋„ ์˜ฌ๋ ค์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์ด ์Šค๋ ˆ๋“œ์—์„œ ๋ช‡ ๋ฒˆ ๋งํ–ˆ์ง€๋งŒ ๋ชจ๋‘๊ฐ€ ์—ฌ์ „ํžˆ localStorage๊ฐ€ ์žˆ๋Š” JWT ๋˜๋Š” JavaScript ์•ก์„ธ์Šค ๊ฐ€๋Šฅํ•œ ์ฟ ํ‚ค์— ์„ธ์…˜ ํ† ํฐ์„ ์ €์žฅํ•ด์„œ๋Š” ์•ˆ ๋œ๋‹ค๋Š” ์ ์„ ์—ฌ์ „ํžˆ ๋ฌด์‹œํ•˜๊ณ  ์žˆ์œผ๋ฉฐ ๋œ ์•ˆ์ „ํ•œ ๋ฐฉ์‹์œผ๋กœ ์„ธ์…˜ ๋ฐ ์ธ์ฆ์„ ์žฌ๋ฐœ๋ช…ํ•˜๋ ค๋Š” ์˜๋„๋กœ ๋ณด์ž…๋‹ˆ๋‹ค. (๊ทธ๋ฆฌ๊ณ  ๊ทธ๊ฒƒ์€ ํด๋ผ์ด์–ธํŠธ ์ธก JS๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค). ๐Ÿ™ƒ๐Ÿคฆ๐Ÿปโ€โ™‚๏ธ

์ด ๊ฒŒ์‹œ๋ฌผ ์˜ ์ž‘์„ฑ์ž๋Š”

๋ถˆํ–‰ํžˆ๋„ ์‚ฌ๋žŒ๋“ค์ด ์ฝ๊ธฐ๋ฅผ ์ค‘๋‹จํ•˜๊ธฐ ์ „์— ๊ธฐ์‚ฌ ๊ธธ์ด์— ๋Œ€ํ•œ ์ƒํ•œ์„ ์„ ์ฐพ์€ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. Reddit ๋ฐ Hacker News์˜ ๋งŽ์€ ๋Œ“๊ธ€ ์ž‘์„ฑ์ž๋Š” ์ด๋ฏธ ํ•ด๊ฒฐ๋˜์—ˆ์œผ๋ฉฐ ๋น„ํ˜„์‹ค์ ์ด๋ผ๋Š” ์‚ฌ์‹ค์„ ์™„์ „ํžˆ ๋ฌด์‹œํ•˜๊ณ  ๋™์ผํ•œ "์†”๋ฃจ์…˜"์„ ๊ณ„์†ํ•ด์„œ ์ œ์•ˆํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์‚ฌ ์ž์ฒด์—์„œ.

์›๋ณธ ๊ธฐ์‚ฌ ์™€ ํ›„์† ๋‹ค์ด์–ด๊ทธ๋žจ ๋ชจ๋‘ ๊ฝค ํ›Œ๋ฅญํ•ฉ๋‹ˆ๋‹ค.

@iaincollins JWT ๋ฐ ์„ธ์…˜ ์—†๋Š” ์•ฑ/์ธ์ฆ์—๋Š” ์žฅ์ ์ด ์žˆ์œผ๋ฉฐ ๋งŽ์€ ์‚ฌ๋žŒ/ํšŒ์‚ฌ(Google ํฌํ•จ)๊ฐ€ ์—ฌ์ „ํžˆ ์‚ฌ์šฉํ•˜๊ธฐ๋ฅผ ์›ํ•ฉ๋‹ˆ๋‹ค. JWT์— ๋Œ€ํ•œ

๋‚˜๋Š” ์ด๊ฒƒ์— ๋Œ€ํ•ด ์•„๋ฌด ์ƒ๊ฐ ์—†์ด ํƒญ์„ ์œ ์ง€ํ•˜๊ณ  ์žˆ์ง€๋งŒ ๋ฒ”์šฉ JS ์ธ์ฆ์— ๋Œ€ํ•œ ๋‚ด ๊ฒฝํ—˜์€ JWT๋ฅผ HttpOnly ์ฟ ํ‚ค์— ์ €์žฅํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์•ฑ์˜ ์„œ๋ฒ„ ์ธก์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฟ ํ‚ค๋ฅผ ์ €์žฅํ•˜๋Š” ์˜ต์…˜์ด ์žˆ๋Š” ๊ฒฝ์šฐ localStorage ๋ฅผ ์‚ฌ์šฉํ•  ์ด์œ ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ €์—์„œ ๊ต์ฐจ ์ถœ์ฒ˜ API ํ˜ธ์ถœ์„ ์œ„ํ•ด JWT์— ์•ก์„ธ์Šคํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ __INITIAL_STATE__ ์œ ํ˜• ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ JWT๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(XSS ์ทจ์•ฝ์„ฑ์— ์ฃผ์˜ํ•˜์ง€๋งŒ ์ตœ์†Œํ•œ "์ €์žฅ"ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ์ธก). ๋™์ผ ์ถœ์ฒ˜ ์•ก์„ธ์Šค์˜ ๊ฒฝ์šฐ ์ฟ ํ‚ค๊ฐ€ ์•ž๋’ค๋กœ ์ „๋‹ฌ๋˜์–ด(axios์˜ ๊ฒฝ์šฐ withCredentials ๋˜๋Š” ๊ฐ€์ ธ์˜ค๊ธฐ์˜ ๊ฒฝ์šฐ credentials: 'include' ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ๊ฐ€์ •) JWT๋ฅผ JS์— ๋„ฃ์„ ํ•„์š”๊ฐ€ ์ „ํ˜€ ์—†์Šต๋‹ˆ๋‹ค. . ์•ฑ์˜ ์„œ๋ฒ„ ์ธก์—์„œ ํ”„๋ก์‹œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ HttpOnly ์ฟ ํ‚ค์—์„œ JWT๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  _๊ทธ๋Ÿฌ๋ฉด_ ๊ต์ฐจ ์ถœ์ฒ˜ API ํ˜ธ์ถœ๋„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ๋„ ์‹คํ–‰ ์ „ ํ˜ธ์ถœ์„ ๋ฌดํšจํ™”ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ์ž์—๊ฒŒ ์žˆ์ง€๋งŒ ๊ฐœ์ธ์ ์œผ๋กœ ๋ฒ”์šฉ ์•ฑ์—๋Š” localStorage๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

@bjunc ์˜ˆ, JWT๋ฅผ ์ €์žฅํ•ด์•ผ ํ•˜๋Š” ์œ„์น˜์— ๋Œ€ํ•œ ๋ชจ๋“  ์˜ต์…˜ ์ค‘์—์„œ(localStorage ๋Œ€ HttpOnly ์ฟ ํ‚ค ๋Œ€ Redux ๋˜๋Š” ๋ฌด์—‡์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ?) ์ œ๊ฐ€ ํ‹€๋ฆด ์ˆ˜๋„ ์žˆ์ง€๋งŒ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋Œ€๋‹ต์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ํ•ญ์ƒ HttpOnly ์ฟ ํ‚ค์—ฌ์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์ˆ˜๋งŽ์€ ๋ธ”๋กœ๊ทธ์™€ ํฌ๋Ÿผ์—์„œ ์ˆ˜์—†์ด ์–ธ๊ธ‰๋œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. (์ƒˆ๋กœ ๊ณ ์นจ ํ† ํฐ์— ๋Œ€ํ•ด ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค. ์•„๋งˆ๋„ HttpOnly ์ฟ ํ‚ค์—๋„ ์ €์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๊นŒ?) JWT๊ฐ€ ์ €์žฅ๋˜๋Š” ์œ„์น˜๋Š” JWT์˜ ๋‚˜๋จธ์ง€ ์žฅ์ /๋‹จ์ ๊ณผ ๋ณ„๊ฐœ๋กœ ํ•ด๊ฒฐ๋œ ๋ฌธ์ œ์—ฌ์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

๋‚˜๋Š” nextjs-starter์™€ next-auth๋ฅผ ์ฑ„ํƒํ•˜๊ธฐ ์ „์— ๋‚ด ํ”„๋กœ์ ํŠธ์—์„œ ๋‹น์‹ ์ด ๋งํ•œ ๊ฒƒ์„ ์–ด๋Š ์ •๋„ ์ •ํ™•ํ•˜๊ฒŒ ํ•˜๋ ค๊ณ  ๋…ธ๋ ฅํ–ˆ์Šต๋‹ˆ๋‹ค. ์˜ค๋žœ ์‹œ๊ฐ„์ด ์ง€๋‚ฌ์ง€๋งŒ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ธฐ์–ตํ•œ๋‹ค๋ฉด ๋‚ด๊ฐ€ ๊ฒช์€ ๋ฌธ์ œ๋Š” ๋‚ด๊ฐ€ ์ธ์ฆํ•œ Express API ์„œ๋ฒ„(express-session ์‚ฌ์šฉ)๊ฐ€ ์„ธ์…˜์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ดˆ๊ธฐํ™”/ํŒจ์น˜ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ์„ธ์…˜์ด ์—ฌ๋Ÿฌ ๋ฒˆ ์ดˆ๊ธฐํ™”๋˜๋Š” ์ด์ƒํ•œ ๋™์ž‘์ด ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. ๋‚˜๋Š” ๋‹น์‹ ์ด ๊ทธ๊ฒƒ์„ ๊ณ ์น  ์ˆ˜ ์žˆ๋‹ค๋ฉด ๋‹น์‹ ์ด ์„ค๋ช…ํ•œ ๊ฒƒ๋ณด๋‹ค ๋งŽ๊ฑฐ๋‚˜ ์ ์€ ์ผ์„ ์žฌ๊ฐœํ•  ์ƒ๊ฐ์ž…๋‹ˆ๋‹ค. ์„ธ์…˜์€ ํ† ํฐ์„ ๋ฌดํšจํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ ๊ฐ™์€ JWT๊ฐ€ ์ œ๊ธฐํ•˜๋Š” ๋‹ค๋ฅธ ๋งŽ์€ ๋ฌธ์ œ๋ฅผ ์ œ๊ฑฐํ•˜๊ธฐ ๋•Œ๋ฌธ์— nextjs-starter ๋ฐ next-auth๋กœ ๊ณ„์† ์ž‘์—…ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‹ค์‹œ ๋งํ•˜์ง€๋งŒ, ๊ณต์‹์ ์ธ ์˜ˆ(๋˜๋Š” ์˜ˆ)๊ฐ€ ๋„์›€์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ‚ค์›Œ๋“œ: ๊ณต์‹, ์ฆ‰ Next.js์˜ ์ž‘์„ฑ์ž๋Š” ๋ชจ๋“  ๊ฐ€๋Šฅ์„ฑ์„ ๊ณ ๋ คํ•˜๊ณ  ์ตœ๊ณ ์˜ ์•„์ด๋””์–ด์™€ ์‚ฌ๋ก€๋ฅผ ์—ฌ๊ธฐ์— ํ†ตํ•ฉํ–ˆ์Šต๋‹ˆ๋‹ค. promise ๋ฐ async/await์™€ ๊ฐ™์€ ์ตœ์‹  ES6/ES7/ES8 ๊ธฐ๋Šฅ์„ ์ด์ƒ์ ์œผ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

@kelleg1 ๊ท€ํ•˜์˜ ๋ฌธ์ œ๋Š” ์ฟ ํ‚ค ์ƒ์„ฑ์˜ ์„ธ๋ถ€ ์‚ฌํ•ญ๊ณผ ๊ด€๋ จ๋œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค๋ฅธ ์„ค์ •( HttpOnly , ๋„๋ฉ”์ธ, ๊ฒฝ๋กœ, ๋งŒ๋ฃŒ ๋“ฑ)์„ ์‚ฌ์šฉํ•˜์—ฌ ์‹ค์ˆ˜๋กœ ๊ฐ™์€ ์ด๋ฆ„์˜ ์—ฌ๋Ÿฌ ์ฟ ํ‚ค๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹น์‹ ์ด ์„ค๋ช…ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ์ด์ƒํ•œ ๋ถ€์ž‘์šฉ์„ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ๋””๋ฒ„๊น…ํ•˜๋Š” ํ•œ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์€ ๊ฐœ๋ฐœ ๋„๊ตฌ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ->์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ฐ™์€ ์ด๋ฆ„์˜ ์ฟ ํ‚ค๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ ๋ณด์ด๋ฉด ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉํ–ฅ์„ ๊ฐ€๋ฆฌํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์–ด์จŒ๋“  ์ €๋Š” ํ•ต์‹ฌ ํŒ€์ด ์•„๋‹ˆ๋ฏ€๋กœ "๊ณต์‹" ๊ธฐ์—ฌ๋ฅผ ๋„์šธ ์ˆ˜ ์—†์ง€๋งŒ(์‚ฌ์‹ค ์ €๋Š” Next.js๊ฐ€ ์•„๋‹Œ Nuxt.js๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค) ์›์น™์€ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. ๋‚˜๋Š” ์ด๊ฒƒ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ช‡ ๊ฐ€์ง€ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์‹คํ—˜ํ–ˆ์Šต๋‹ˆ๋‹ค(JWT, ์ฟ ํ‚ค, CSFR, websocket CSWSH, localStorage ๋“ฑ์˜ ์žฅ๋‹จ์ ์„ ํ‰๊ฐ€). ๋‚˜๋Š” ๊ถ๊ทน์ ์œผ๋กœ Next/Nuxt์˜ ๋ณดํŽธ์ ์ธ ํŠน์„ฑ์ด HttpOnly JWT ์ฟ ํ‚ค์˜ ์‚ฌ์šฉ์— ์ ํ•ฉํ•˜๋‹ค๋Š” ๊ฒฐ๋ก ์— ๋„๋‹ฌํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์€ ๋‹ค๋ฅธ ๊ฒฐ๋ก ์— ๋„๋‹ฌํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ ์ €๋Š” ๊ฐœ์ธ์ ์œผ๋กœ "๋ง™์†Œ์‚ฌ, JWT๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค. JWT๊ฐ€ ์•”์„ ์œ ๋ฐœํ•œ๋‹ค๋Š” ๊ธฐ์‚ฌ๋ฅผ ์ฝ์ง€ ์•Š์•˜์Šต๋‹ˆ๊นŒ?"์˜ ์ง„์˜์— ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

@iaincollins ์ด๊ฒƒ์„ ๋‹ค์‹œ ๊ฐ€์ ธ์™€์„œ ์œ ๊ฐ์ด์ง€๋งŒ ์›น์˜ ๋ชจ๋“  ๋‹จ์ผ ์ž์Šต์„œ๋Š” localStorage๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ† ํฐ์„ ์ €์žฅํ•˜๋Š”๋ฐ ์•ˆ์ „ํ•˜์ง€

์ฟ ํ‚ค! :NS
์šฐ๋ฆฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

// auth.js
async signIn(res, info) {
    const { email, password } = info;
    const result = await this.service.signIn(email, password);
    if (!result) {
      res.status(HttpStatus.BAD_REQUEST).json({ success: false, message: 'Wrong email and/or password' });
      return;
    }
  const payload = {
    scope: 'appUser',
    userId: user.email,
    language: user.language,
    iat: moment().unix(),
    exp: moment().add(7, 'days').unix(),
    type: user.type,
    status: user.status
  };
    const token = jwt.sign(payload, ...);

    res.cookie('token', token, { domain: 'yourdomain.com', path: '/', secure: true, httpOnly: true, maxAge: 24 * 60 * 60 * 1000 * 7, signed: true });

    res.status(HttpStatus.OK).json({});
  }

// pages/index.js

class Index extends PureComponent {
  render() {
    return (
      <Layout title="home">
        <Home />
      </Layout>
    );
  }
}

Index.getInitialProps = async ({ req, res }) => {
  const auth = req ? getServerSideToken(req) : getClientSideToken();
  return { auth };
}
export default Index;

// utils/auth.js
const decode = ({ token }) => {
  const decoded = jwt.decode(token);
  const { userId, type = 'anonymous', status, language = 'en' } = decoded || {};
  return { user: { email: userId, type, status, language } };
};

export const getServerSideToken = (req) => {
  const { signedCookies } = req;

  if (!signedCookies) {
    return {};
  }
  try {
    return decode(signedCookies);
  } catch (parseError) {
    console.error(parseError, signedCookies);
    return {};
  }
};

export const getClientSideToken = () => {
  if (typeof window !== 'undefined') {
    const user = window.__USER__ || {};
    return { user };
  }
  return { user: {} };
};

// pages/_document.js
export default class extends Document {
  static async getInitialProps(ctx) {
    const props = await Document.getInitialProps(ctx);
    const info = getServerSideToken(ctx.req);
    return { ...props, ...info };
  }

  render() {
    const { user = {} } = this.props;
    const json = JSON.stringify(user);
    return (
      <html lang="en">
        <Head>
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          <meta charSet="utf-8" />
          <link href="//fonts.googleapis.com/css?family=Kadwa:400,700|Work+Sans:100,300,600" rel="stylesheet" />
          <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.14/semantic.min.css" />
          <link rel="stylesheet" href="/static/site.css" />
        </Head>
        <body>
          <Main />
          <script
            dangerouslySetInnerHTML={{ __html: `__USER__ = ${json};` }}
          />
          <NextScript />
        </body>
      </html>
    );
  }
}

// somewhere.js
import axios from 'axios';
axios.defaults.withCredentials = true;
axios.post(`${apiBaseUrl}/some/path`, data)...

// somemiddleware.js
      const { signedCookies = {} } = req;
      const { token = '' } = signedCookies;
      if (token) {
        try {
          const info = jwt.verify(token, ...);
          req.user = await this.getUser(info.userId);
          if (req.user) {
            next();
            return;
          }
        } catch (err) {
          console.error({ permissionError: err });
          return res.status(500).json({...});
        }
      }
      return res.status(403).json({...});

ํด๋ผ์ด์–ธํŠธ ์ธก ์ž๋ฐ” ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ํ† ํฐ์„ ์ ˆ๋Œ€ ๋ณผ ์ˆ˜ ์—†๊ณ  ์ฟ ํ‚ค๊ฐ€ httpOnly์ด๊ณ , ์•ˆ์ „ํ•˜๊ณ , ์„œ๋ช…๋˜๊ณ  axios ๋˜๋Š” ๋‹ค๋ฅธ ์‚ฌ๋žŒ์— ์˜ํ•ด ์ž๋™์œผ๋กœ ์ „์†ก๋˜๊ธฐ ๋•Œ๋ฌธ์— ์•ฝ๊ฐ„ ํˆฌ๋ฐ•ํ•˜์ง€๋งŒ ๊ฝค ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค...

์•ˆ๋…•ํ•˜์„ธ์š”, ๋ฐฉ๊ธˆ ์ฟ ํ‚ค์™€ redux๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ ์˜ˆ์ œ๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ๋ฅผ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค https://github.com/zeit/next.js/pull/4011

์ด ๋ฌธ์ œ๋Š” ์–ด๋””์—์„œ ๋๋‚ฌ์Šต๋‹ˆ๊นŒ? ๊ณต์‹ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์—์„œ Express ์ธ์ฆ ์˜ˆ์ œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

์ฟ ํ‚ค์™€ ์„ธ์…˜์— ๋Œ€ํ•ด ๋งŽ์ด ์ด์•ผ๊ธฐํ•˜์ง€๋งŒ API์— ๋„๋‹ฌํ•ด์•ผ ํ•  ๋•Œ ๋‚ด ๊ธฐ๋ณธ ๋ชจ๋ฐ”์ผ ์•ฑ์—์„œ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๊นŒ?

@jackjwilliams ๋Š” ๋ชจ๋ฐ”์ผ ์•ฑ HTTP ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋„ ์ฟ ํ‚ค๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๊นŒ?
https://stackoverflow.com/questions/1660927/iphone-make-post-request-handle-cookie
https://stackoverflow.com/questions/678630/how-do-i-make-an-http-request-using-cookies-on-android

ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์ด๊ฒƒ์ด JWT์˜ ์šฉ๋„์ž…๋‹ˆ๋‹ค.

๋‚˜๋Š” ์ด ์ „์ฒด ์Šค๋ ˆ๋“œ๋ฅผ ์ž์„ธํžˆ ์ฝ์—ˆ๊ณ  ๋‚ด ๋ฐœ๊ฒฌ์„ ์š”์•ฝํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

๊ฒฌ๊ณ ํ•œ ์ธ์ฆ ๋ชจ๋“ˆ์„ ์ œ์™ธํ•˜๊ณ  ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ๋‘ ๊ฐ€์ง€ ์‹œ์ž‘ ํ”„๋กœ์ ํŠธ๊ฐ€ ์žˆ๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค.

@iaincollins ์™€ @nmaro์˜ ํ›Œ๋ฅญํ•œ ์ž‘ํ’ˆ!

@curran๋‹˜ ๊ฐ์‚ฌ

์ด Pull Request https://github.com/iaincollins/nextjs-starter/pull/86 ์—์„œ nextjs-starter์˜ ๋ถˆํ•„์š”ํ•œ ๋ถ€๋ถ„์„ ์ œ๊ฑฐํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“  ๋ชจ๋“  ์ฝ”๋“œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ฌธ์„œํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ Next.js ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ๋Œ€ํ•œ ๊ฒฌ๊ณ ํ•œ ๋ฒ ์–ด๋ณธ ์ธ์ฆ ์˜ˆ์ œ์— ๋” ๊ฐ€๊นŒ์›Œ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ตœ๊ทผ์— Office 365๋กœ OAuth๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์š”๊ตฌ ์‚ฌํ•ญ์ด ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์—ฌ๊ธฐ์— ํ•จ๊ป˜ ๋˜์ง„ ์•„์ฃผ ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋ฅผ ๊ณต์œ ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค. ์ž‘์—…์ด ํ•„์š”ํ•˜์ง€๋งŒ(์œ„์˜ ๋ช‡ ๊ฐ€์ง€ ์˜ˆ๋งŒํผ ๊ฐœ๋ฐœ๋˜์ง€๋Š” ์•Š์Œ) ๊ฒฐ๊ตญ ๋‹ค์–‘ํ•œ OAuth ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์ผ๋ฐ˜ํ™”๋  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. Next.js ์˜ˆ์ œ ์ €์žฅ์†Œ์— ์ด๋ฏธ ํ‘œ์‹œ๋œ ๋ช‡ ๊ฐ€์ง€ ์˜ˆ์ œ์™€ ์—ฌ๊ธฐ์— ์žˆ๋Š” ๋ณดํ˜ธ ๊ฒฝ๋กœ ์Šค๋ ˆ๋“œ๋ฅผ ํ™œ์šฉ

ํด๋ผ์ด์–ธํŠธ ๋ฐ ์„œ๋ฒ„ ์ธก์—์„œ ์ž‘๋™ํ•˜๋Š” ๊ฐ„๋‹จํ•œ JWT ํ† ํฐํ™”๋œ ์ธ์ฆ์— ๊ด€์‹ฌ์ด ์žˆ๋Š” ์‚ฌ๋žŒ์„ ์œ„ํ•ด Spectrum ์ฑ„ํŒ…์—์„œ ๋„์›€์„ ์–ป์—ˆ๊ณ  ์ด๋ฅผ ์—ฌ๋Ÿฌ๋ถ„ ๋ชจ๋‘์™€ ๊ณต์œ ํ•  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ํ”ผ๋“œ๋ฐฑ์€ ํ•ญ์ƒ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

https://github.com/bgold0/nextjs-auth-skeleton

์•ˆ๋…• ์–˜๋“ค์•„!
๋‹ค์Œ์€ ๋‚ด๊ฐ€ ๋ช‡ ๋‹ฌ ์ „์— ๊ตฌ์ถ•ํ•œ next.js๋ฅผ ์‚ฌ์šฉํ•œ ์ธ์ฆ์˜ ๋˜ ๋‹ค๋ฅธ ์˜ˆ์ž…๋‹ˆ๋‹ค. ๋ˆ„๊ตฐ๊ฐ€ ์œ ์šฉํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

https://github.com/alan2207/nextjs-jwt-authentication

Ooth 2.0 ์€ ์ƒˆ๋กญ๊ณ  ๋” ๋‚˜์€ ๋ฌธ์„œ ์™€ ํ•จ๊ป˜ ์ถœ์‹œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Ooth๋Š” node.js์šฉ์œผ๋กœ ๊ตฌ์ถ•๋œ ์‚ฌ์šฉ์ž ID ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค(next.js๋ฅผ ์—ผ๋‘์— ๋‘๊ณ ).

ํ˜„์žฌ ์ง€์›๋˜๋Š” ์ „๋žต:

  • ๊ธฐ๋ณธ: ์‚ฌ์šฉ์ž ์ด๋ฆ„/๋น„๋ฐ€๋ฒˆํ˜ธ(์ด๋ฉ”์ผ ํ™•์ธ, ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ถ„์‹ค ๋“ฑ ํฌํ•จ), ๊ฒŒ์ŠคํŠธ, ํŽ˜์ด์Šค๋ถ, ๊ตฌ๊ธ€
  • ๋ณด์กฐ: ์ฟ ํ‚ค ๊ธฐ๋ฐ˜ ์„ธ์…˜, JWT

next.js( ์†Œ์Šค ์ฝ”๋“œ )์™€ ํ•จ๊ป˜ ๋ชจ๋“  ๊ฒƒ์„ ํ†ตํ•ฉํ•˜๋Š” ์ด ๋ผ์ด๋ธŒ ์˜ˆ์ œ ๋ฅผ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค.

์—ฌ๊ธฐ์— ์žˆ๋Š” ๋งŽ์€ ์ธ์ฆ ์ƒ˜ํ”Œ(๋ฐ examples ํด๋”์— ์žˆ์Œ)์€ getInitialProps ์—์„œ ์‚ฌ์šฉ์ž session/token/etc ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋‚ด๊ฐ€ ์ดํ•ดํ•˜๋Š” ํ•œ ํŽ˜์ด์ง€๊ฐ€ ์„œ๋ฒ„ ์ธก์—์„œ ๋ Œ๋”๋ง๋˜๋ฉด ์‚ฌ์šฉ์ž ์„ธ์…˜ ์ •๋ณด๊ฐ€ HTML ํŽ˜์ด์ง€ ์‘๋‹ต์˜ ์ผ๋ถ€๋กœ( NEXT_DATA ์ผ๋ถ€๋กœ) ๋ธŒ๋ผ์šฐ์ €๋กœ ์ „์†ก๋ฉ๋‹ˆ๋‹ค.

getInitialProps ๊ฐ€ ์„œ๋ฒ„ ์ธก์—์„œ ์‹คํ–‰๋  ๋•Œ ์ด ํŒจํ„ด์— ๋‘ ๊ฐ€์ง€ ๋ณด์•ˆ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
1) ์‚ฌ์šฉ์ž ์„ธ์…˜์€ ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„์—์„œ ๋ธŒ๋ผ์šฐ์ €๋กœ ์ „์†ก๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌํ•œ ์š”์ฒญ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ http:// ํ•˜์ง€๋ฅผ https:// , ์‚ฌ์šฉ์ž์˜ ํ† ํฐ ๋“ฑ์€ ์ผ๋ฐ˜ ํ…์ŠคํŠธ๋กœ ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด ๋…ธ์ถœ ๋  ๊ฒƒ์ด๋‹ค.

2) ์‚ฌ์šฉ์ž ์„ธ์…˜์€ HTML ํŽ˜์ด์ง€์˜ ์ผ๋ถ€๋กœ ์„œ๋ฒ„์—์„œ ๋ธŒ๋ผ์šฐ์ €๋กœ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค( NEXT_DATA ์Šคํฌ๋ฆฝํŠธ ํƒœ๊ทธ์—์„œ). ์‚ฌ์šฉ์ž์˜ ํ† ํฐ/๋“ฑ์„ HTML ํŽ˜์ด์ง€์— ์ง์ ‘ ๋ฐฐ์น˜ํ•˜๋Š” ๊ฒƒ์€ ์œ„ํ—˜ํ•ด ๋ณด์ž…๋‹ˆ๋‹ค. ํŠนํžˆ ํŽ˜์ด์ง€๊ฐ€ ๊ตฌ๋ฌธ ๋ถ„์„๋˜๊ณ  ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ Œ๋”๋ง๋˜๊ณ  ๋‹ค๋ฅธ ํƒ€์‚ฌ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์ด์ œ ์‹คํ–‰ ์ค‘์ผ ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ์—๋Š” ๋”์šฑ ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋Š” ์ด๋ฏธ ํ•ด๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๊นŒ? ์ด๋Ÿฌํ•œ ์œ„ํ˜‘์— ๋Œ€ํ•œ ์™„ํ™” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๊นŒ?

๊ทธ๋ž˜์„œ ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ ๋‚ด ์˜ˆ๋ฅผ ์ฐธ์กฐ ํ•˜์‹ญ์‹œ์˜ค.

๋งˆ์นจ๋‚ด! ์—ฌ๊ธฐ์—์„œ ๋‹ค์Œ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ํฌํ•จ๋œ ์™„์ „ํžˆ ๊ณ ์ •๋œ next.js ์ธ์ฆ ์˜ˆ์ œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋‹ค์Œ.js
  • ์ธ์ฆ ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค(ooth)
  • API(graphql)
  • ์„ธ์…˜ ์Šคํ† ๋ฆฌ์ง€(redis)
  • ์ž‘์€ ์—ญ๋ฐฉํ–ฅ ํ”„๋ก์‹œ

๋ชจ๋“  ๊ฒƒ์ด docker-compose๋กœ ๊ฒฐํ•ฉ๋ฉ๋‹ˆ๋‹ค.

JWT์— ๋Œ€ํ•œ ๋น ๋ฅธ ๋ฉ”๋ชจ. JWT๋Š” ์ƒํƒœ ๋น„์ €์žฅ, ๋ชจ๋ฐ”์ผ์— ์ ํ•ฉํ•˜๊ณ  ํ•œ ๋„๋ฉ”์ธ์—์„œ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์œผ๋กœ ์ž๊ฒฉ ์ฆ๋ช…์„ ์ „๋‹ฌํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์— ์ข‹์€ ์ด์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ฃผ์š” ๋‹จ์ ์€ ๋ธŒ๋ผ์šฐ์ €๋ฅผ XSS์— ๋…ธ์ถœํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ์˜ˆ์—์„œ๋Š” ์ˆœ์ˆ˜ํ•œ ์ฟ ํ‚ค ์„ธ์…˜ ๊ธฐ๋ฐ˜ ์†”๋ฃจ์…˜์„ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ(๋ชจ๋“  ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๊ฐ€ ๋™์ผํ•œ ๋„๋ฉ”์ธ์—์„œ ์‹คํ–‰๋จ)์™€ ๊ณต์œ  ์„ธ์…˜ ์Šคํ† ๋ฆฌ์ง€ ๋•๋ถ„์— ์—ฌ์ „ํžˆ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์—์„œ ์ผ์„ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

https://github.com/nmaro/staart/tree/master/examples/staart
๋ผ์ด๋ธŒ ์˜ˆ๋Š” ํ‰์†Œ์™€ ๊ฐ™์ด https://staart.nmr.io์ž…๋‹ˆ๋‹ค.

JWT๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋ณธ์งˆ์ ์œผ๋กœ XSS์— "๋…ธ์ถœ"๋˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์„ ๋ช…ํ™•ํžˆ ํ•˜๋Š” ๊ฒƒ์ด ํ˜„๋ช…ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์˜คํžˆ๋ ค ์‚ฌ์ดํŠธ์— XSS ์ทจ์•ฝ์ ์ด ์žˆ๋Š” ๊ฒฝ์šฐ(JWT ์ž์ฒด๋กœ ์ธํ•œ ๊ฒƒ์ด ์•„๋‹˜) JWT๊ฐ€ ์†์ƒ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(๋‹ค๋ฅธ ์Šคํฌ๋ฆฝํŠธ ์•ก์„ธ์Šค ๊ฐ€๋Šฅ ์ •๋ณด์™€ ํ•จ๊ป˜). ๋ฐ˜๋ฉด httpOnly ์ฟ ํ‚ค๋Š” ์•ก์„ธ์Šคํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. JWT๋ฅผ httpOnly ์ฟ ํ‚ค์˜ ๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์žŠ์ง€ ๋งˆ์‹ญ์‹œ์˜ค!

์ฟ ํ‚ค ์ „์šฉ ์†”๋ฃจ์…˜์€ ๋™์ผํ•œ ๋„๋ฉ”์ธ ํ†ต์‹ ์— ๋Œ€ํ•ด ์ž˜ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ํ—ค๋“œ๋ฆฌ์Šค API(์˜ˆ: example.com ํ˜ธ์ถœ api.example.com )๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ํ”„๋ก์‹œ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์›ํ•˜์ง€ ์•Š๋Š” ํ•œ ์ด๋Š” ์‹ค์ œ๋กœ ์†”๋ฃจ์…˜์ด ์•„๋‹™๋‹ˆ๋‹ค. ์š”์ฒญ example.com ์— api.example.com ๋กœ API ํ˜ธ์ถœ์„ํ•˜์—ฌ example.com ์™€ (ํ”„๋กœ / ์ฃ„์ˆ˜์˜ ๊ทธ๊ฒƒ์˜ ์ž์‹ ์˜ ์„ธํŠธ์™€ ํ•จ๊ป˜ ์ œ๊ณต) ์š”์ฒญ์— ์ถ”๊ฐ€ ๋œ ์ฟ ํ‚ค์— ์ „๋‹ฌํ•  .

์ €๋Š” ๊ฐœ์ธ์ ์œผ๋กœ JWT์˜ ๋‹จ์ ์ด ์ง€๋‚˜์น˜๊ฒŒ ๊ณผ์žฅ๋˜์–ด ์žˆ์œผ๋ฉฐ ์ผ๋ฐ˜์ ์œผ๋กœ ๊ตฌํ˜„๋˜๋Š” ๋งŽ์€ ๋ณดํ˜ธ ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ์‰ฝ๊ฒŒ ์™„ํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜๊ธฐ ์ „์— ์†์ƒ๋œ ๊ฒฝ์šฐ jti ์ฒญ๊ตฌ(์˜ˆ: Version4 UUID)๋ฅผ ์ฐธ์กฐํ•˜๋Š” ํ† ํฐ ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

@bjunc ๋„ค, ๋ช…ํ™•ํžˆ ํ•ด ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ๋งž์Šต๋‹ˆ๋‹ค. JWT ์ž์ฒด๊ฐ€ XSS์— ๋…ธ์ถœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ท€ํ•˜์˜ ๋‹ค๋ฅธ ๊ด€์ฐฐ์— ๊ด€ํ•ด์„œ๋Š” ๊ฐ๊ฐ ํ•จ์ •์ด ์žˆ์Œ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

JWT๋ฅผ httpOnly ์ฟ ํ‚ค์˜ ๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์žŠ์ง€ ๋งˆ์‹ญ์‹œ์˜ค!

์˜ˆ, ๋„๋ฉ”์ธ ๊ฐ„์— ์ž๊ฒฉ ์ฆ๋ช…์„ ์ „์†กํ•˜๋Š” JWT์˜ ํ•œ ๊ฐ€์ง€ ์ด์ ์„ ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

ํ† ํฐ ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ

๊ทธ๋ฆฌ๊ณ  ์ƒˆ๋กœ ๊ณ ์นจ ํ† ํฐ์˜ ๋‹ค๋ฅธ ์ผ๋ฐ˜์ ์ธ ๊ด€ํ–‰์€ JWT๊ฐ€ ์‹ค์ œ๋กœ ์ƒํƒœ ๋น„์ €์žฅ์ด๋ผ๋Š” ๋‹ค๋ฅธ ์ด์ ์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ ์ƒํ™ฉ์— ๋Œ€ํ•œ ๋‚˜์˜ ์ดํ•ด์ž…๋‹ˆ๋‹ค.

  • ๊ต์ฐจ ์œ„์น˜ ์ธ์ฆ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ JWT <-๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ ๊ฐ€๋Šฅํ•˜๋ฉด ๋ณด์•ˆ ๋•Œ๋ฌธ์— ํ”ผํ•˜์‹ญ์‹œ์˜ค.
  • ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์•„๋‹ˆ๊ณ  XSS ๋ฌธ์ œ๊ฐ€ ์—†๋Š” ํด๋ผ์ด์–ธํŠธ(์˜ˆ: ๋ฐ์Šคํฌํ†ฑ ๋˜๋Š” ๋ชจ๋ฐ”์ผ ์•ฑ)์—์„œ ์ธ์ฆํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ JWT๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด JWT ๊ธฐ๋ฐ˜ ์†”๋ฃจ์…˜์ด ๋œ ์•ˆ์ „ํ•˜๊ฑฐ๋‚˜(XSS) ์‹ค์ œ๋กœ ์ƒํƒœ ๋น„์ €์žฅ(๋ธ”๋ž™๋ฆฌ์ŠคํŠธ)์ด ์•„๋‹ˆ๊ฑฐ๋‚˜ ํ”„๋ก์‹œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  ๊ฒƒ์„ ๋™์ผํ•œ ๋„๋ฉ”์ธ ์•„๋ž˜์— ์œ ์ง€ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฟ ํ‚ค ๊ธฐ๋ฐ˜ ์„ธ์…˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

Next.js ์ธ์ฆ/์‚ฌ์šฉ์ž ๊ณ„์ •์— ๋Œ€ํ•œ Medium ๊ธฐ์‚ฌ๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.
์ด๊ฒƒ์€ ๊ด‘๋ฒ”์œ„ํ•œ ํŠœํ† ๋ฆฌ์–ผ์ด์ž ๊ฑฐ์˜ 2๋…„ ๊ฐ„์˜ ์—ฌ๊ฐ€ ์‹œ๊ฐ„ ๊ฐœ๋ฐœ ๋ฐ ์‚ฌ๊ณ ์—์„œ ์–ป์€ ๋‚ด ๋ธŒ๋ ˆ์ธ ๋คํ”„์ž…๋‹ˆ๋‹ค(์ด ๋ฌธ์ œ์— ๋Œ€ํ•œ ์ฒซ ๋ฒˆ์งธ ๋…ผํ‰ ์€ 2017๋…„ 2์›”์— ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค).

https://medium.com/the-ideal-system/user-accounts-with-next-js-an-extensive-tutorial-6831cdaed16b

JWT๋ฅผ ๋ณด์•ˆ ์ˆ˜์ค€์ด ๋‚ฎ์€ ๊ฒƒ๊ณผ ๊ณ„์† ์—ฐ๊ด€์‹œํ‚ต๋‹ˆ๋‹ค. JWT๋Š” ๊ท€ํ•˜์˜ ์‚ฌ์ดํŠธ๋ฅผ ๋œ ์•ˆ์ „ํ•˜๊ฒŒ ๋งŒ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ๊ทธ ์ž์ฒด๋กœ XSS ์ทจ์•ฝ์ ์ด ์•„๋‹™๋‹ˆ๋‹ค. XSS ์ต์Šคํ”Œ๋กœ์ž‡์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๊ด€๊ณ„์—†์ด ๋™์ผํ•œ ๋ฌธ์ œ์— ์ง๋ฉดํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. httpOnly ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๋”๋ผ๋„ ๊ณต๊ฒฉ์ž๋Š” ์ฟ ํ‚ค ๊ฐ’์„ ์ฝ์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์„ธ์…˜ ์ฟ ํ‚ค๋ฅผ ์ž๋™์œผ๋กœ ์ „๋‹ฌํ•˜๋Š” XHR ์š”์ฒญ๊ณผ ๊ฐ™์€ ์ž„์˜์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. CSRF ๊ณต๊ฒฉ). API๊ฐ€ ๋„๋ฉ”์ธ ๊ฐ„์ด๊ณ  ๋ธŒ๋ผ์šฐ์ €์—์„œ ์„œ๋ฒ„๋กœ ์š”์ฒญํ•˜๋Š” ๊ฒฝ์šฐ JWT๋Š” ์–ด์จŒ๋“  ์ฝ”๋“œ์˜ ์–ด๋”˜๊ฐ€์— ์žˆ์Šต๋‹ˆ๋‹ค(์ดˆ๊ธฐ ์ƒํƒœ ๋˜๋Š” localStorage). Axios๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๊ณต๊ฒฉ์ž๊ฐ€ ์ธ์ฆ์— ๋Œ€ํ•ด ๊ฑฑ์ •ํ•˜์ง€ ์•Š๊ณ  Axios ์š”์ฒญ๋งŒ ํ•˜๋ฉด ๋˜๋Š” ์ „์—ญ ๊ธฐ๋ณธ๊ฐ’์„ ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ CSRF์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•˜์ง€ ์•Š๊ณ  XSS์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ธ์ฆ ์ฟ ํ‚ค๊ฐ€ ํŠน๋ณ„ํžˆ ํƒ€๊ฒŸํŒ…๋˜๋Š” ๊ฒฝ์šฐ. ์—ญ๋ฐฉํ–ฅ ํ”„๋ก์‹œ ์„ค์ •์„ ์‚ฌ์šฉํ•˜๋”๋ผ๋„ CSRF ๊ณต๊ฒฉ์„ ํ†ตํ•ด ๊ณต๊ฒฉ์ž๋Š” API์— ๋Œ€ํ•ด ์ธ์ฆ๋œ ์š”์ฒญ์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Btw, JWT๋ฅผ ์ฟ ํ‚ค์— ๋„ฃ์—ˆ๋‹ค๊ณ  ํ•ด์„œ(์˜ˆ: ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ–ˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ์•„๋Š” ๊ฒฝ์šฐ) ๋„๋ฉ”์ธ ๊ฐ„ ์„œ๋ฒ„ ๊ฐ„ ์ฟ ํ‚ค ๊ฐ’(์˜ˆ: JWT)์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์˜๋ฏธ๋Š” ์•„๋‹™๋‹ˆ๋‹ค. ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ API-์„œ๋ฒ„ ์•ก์„ธ์Šค(์—ญ๋ฐฉํ–ฅ ํ”„๋ก์‹œ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ๋„ ์ž‘๋™ํ•จ). ๋˜ํ•œ ๋ธŒ๋ผ์šฐ์ €์—์„œ API ์„œ๋ฒ„๋กœ์˜ ์š”์ฒญ์— ๋Œ€ํ•ด JWT๋ฅผ ์ดˆ๊ธฐ ์ƒํƒœ๋กœ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ๋„ ๊ธˆ์ง€๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ๋“ค์€ ์ƒํ˜ธ ๋ฐฐํƒ€์ ์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ œ์ณ๋‘๊ณ , ๋‚˜๋Š” "์ƒํƒœ ๋น„์ €์žฅ" JWT์— ๋Œ€ํ•œ ์•„์ด๋””์–ด๊ฐ€ ๋Œ€๋ถ€๋ถ„์˜ ์‚ฌ์šฉ ์‚ฌ๋ก€์— ๋Œ€ํ•ด ๊ณผ๋Œ€ํ‰๊ฐ€๋˜๊ณ  ์ œํ•œ์ ์ด๋ผ๋Š” ๊ฒƒ์„ ์•Œ์•˜์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด:

  • ๋ฆฌ์†Œ์Šค ๊ธฐ๋ฐ˜/๋™์  ๊ถŒํ•œ(์˜ˆ: " can edit Post "์ด ์•„๋‹ˆ๋ผ " can edit Post:1634 ").
  • ์‚ฌ์šฉ์ž์˜ ๊ณ„์ •์ด ์ฐจ๋‹จ/์‚ญ์ œ๋œ ๊ฒฝ์šฐ ์–ด๋–ป๊ฒŒ ํ•ฉ๋‹ˆ๊นŒ?
  • ์›”๊ฐ„ ๊ตฌ๋…๋ฃŒ๋ฅผ ์ง€๋ถˆํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๊ธฐ๋Šฅ์„ ์กฐ์ ˆํ•˜๋Š” ๊ฒƒ์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?
  • ํ† ํฐ์„ ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ์— ์˜ฌ๋ ธ์Šต๋‹ˆ๊นŒ(์œ„ ์ฐธ์กฐ)?

JWT์— ๋ชจ๋“  ๊ฒƒ์„ ๊ตฝ๋Š” ๊ฒƒ์ด ์•„๋‹™๋‹ˆ๋‹ค. ์ฆ‰, ์•Œ์•„๋‚ด๊ธฐ ์œ„ํ•ด ๋„๋ฉ”์ธ ๊ณ„์ธต(์˜ˆ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค)์— ๋“ค์–ด๊ฐ€์•ผ ํ•จ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋ฐฉ๊ธˆ ๋ฆฌ์†Œ์Šค๋ฅผ ๋กœ๋“œํ–ˆ์œผ๋ฏ€๋กœ ๋‚˜๋จธ์ง€ ์•ฑ์ด ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๋Š” ์œ„์น˜์— ๋ฆฌ์†Œ์Šค๋ฅผ ๋ฐฐ์น˜ํ•˜๋ฉด ์ด์ œ ์ƒํƒœ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ์ฃผ์ œ์— ๋Œ€ํ•ด ์•Œ์•„์•ผ ํ•  ๋ชจ๋“  ๊ฒƒ์ด ํ† ํฐ์— ํฌํ•จ๋  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋Š” ๊ฒƒ์€ ์ •๋ง ์–ด๋ฆฌ์„์€ ์ผ์ž…๋‹ˆ๋‹ค. ๋™์‹œ์— ๋Œ€๋ถ€๋ถ„์˜ ์ต๋ช…์„ฑ๊ณผ ๊ฒฝ๋Ÿ‰์„ฑ์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. ๋„ˆ๋ฌด ๋งŽ์ด ๋ฒ—์–ด๋‚˜์ง€ ์•Š๊ณ  ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ ์—์„œ "์ƒํƒœ ๋น„์ €์žฅ" ์š”์ฒญ์— ๋Œ€ํ•œ ์ฃผ์žฅ์ด ์žˆ์ง€๋งŒ ๋น„์‹ค์šฉ์ ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. .

๊ทธ ๋™์•ˆ Ooth ์ธ์ฆ ์ „๋žต์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(๊ตต๊ฒŒ ํ‘œ์‹œ๋œ ์ƒˆ ํ•ญ๋ชฉ).

  • ๋กœ์ปฌ(์‚ฌ์šฉ์ž ์ด๋ฆ„/์ด๋ฉ”์ผ/๋น„๋ฐ€๋ฒˆํ˜ธ)
  • ํŽ˜์ด์Šค๋ถ
  • Google
  • ์†๋‹˜
  • ํŒจํŠธ๋ฆฌ์˜จ
  • ํŠธ์œ„ํ„ฐ
  • Authy(Twilio) - SMS๋ฅผ ํ†ตํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š”

@jaredpalmer ๋‹น์‹ ์ด ์“ด
php์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Next์˜ ์›์ž ๋‹จ์œ„๋Š” ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค. ๊ฐ€์žฅ ๋ฉ‹์ง„ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜๋Š” ์š”์ฒญ๋œ ๊ฒฝ์šฐ์—๋งŒ ๊ฐ ํŽ˜์ด์ง€๋ฅผ ์ง€์—ฐ ๋กœ๋“œํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ์ธก ์ธ์ฆ๋งŒ ์žˆ์ง€๋งŒ ์„œ๋ฒ„ ๋ Œ๋”๋ง์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ณดํ˜ธ๋œ ํŽ˜์ด์ง€์˜ js๊ฐ€ ์‹ค์ œ๋กœ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋‹ค์šด๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค. ์•ž์œผ๋กœ Next๊ฐ€ ์„œ๋ฒ„ ์›Œํฌํ”Œ๋กœ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง ๋ฐ ๋ฆฌ๋””๋ ‰์…˜์„ ์ฐจ๋‹จํ•˜์—ฌ ์ด๋ฅผ ์™„์ „ํžˆ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—๋Š” ์ฟ ํ‚ค, ์„ธ์…˜ ๋ฐ AFAIK ์„ธ์…˜ ์ €์žฅ์†Œ๊ฐ€ ํ•„์š”ํ•˜์ง€๋งŒ ์ด๋Š” ์ด๋Ÿฌํ•œ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์•ฑ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐ ๋“œ๋Š” ๋น„์šฉ์ผ ๋ฟ์ž…๋‹ˆ๋‹ค.

์šฐ๋ฆฌ๋Š” 2๋…„ ํ›„์ž…๋‹ˆ๋‹ค. ๋ณดํ˜ธ๋œ ํŽ˜์ด์ง€์— ๋Œ€ํ•œ js ๋กœ๋“œ๋ฅผ ๋ฐฉ์ง€ํ•˜๋Š” ์„œ๋ฒ„ ์›Œํฌํ”Œ๋กœ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?
@timneutkens ๋Š” ๋ณดํ˜ธ๋œ ์ฝ˜ํ…์ธ ๋ฅผ ๋‹ค๋ฅธ ์˜์—ญ์— ๋ฐฐ์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?
๋ณดํ˜ธ๋œ ์ฝ˜ํ…์ธ ์— ๋Œ€ํ•œ ์•ก์„ธ์Šค๋ฅผ ์™„์ „ํžˆ ์ฐจ๋‹จํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ•ฉ๋‹ˆ๊นŒ?

@lishine ํŽ˜์ด์ง€์˜ getInitialProps ์— ServerResponse ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ถŒํ•œ์ด ์—†๋Š” ์‚ฌ๋žŒ์„ ์‰ฝ๊ฒŒ ๋ฆฌ๋””๋ ‰์…˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

redux๋ฅผ ์‚ฌ์šฉํ•œ ์ธ์ฆ์˜ ์˜ˆ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

redux๋ฅผ ์‚ฌ์šฉํ•œ ์ธ์ฆ์˜ ์˜ˆ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

redux๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด ์˜ˆ์ œ๋ฅผ ์‹œ๋„ํ•˜๊ณ  ๊ทธ๊ฒƒ์ด ํšจ๊ณผ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค...
์ด ํ•ญ๋ชฉ์˜ ์–ด๋”˜๊ฐ€์—์„œ ์ฐพ์„ ์ˆ˜ ์žˆ์ง€๋งŒ ์ฐพ์„ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ ์—ฌ๊ธฐ์—์„œ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
https://github.com/alan2207/nextjs-jwt-authentication

Virtual DOM์€ LOGOUT-LOGIN ์ž‘์—… ํ›„ ์ด์ „ ๊ฒฐ๊ณผ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— Server Side API ํ˜ธ์ถœ ๊ฒฐ๊ณผ getInitialProps๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ด๊ฒƒ์ด ๋” ๋ณต์žกํ•œ ๋ฌธ์ œ๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ํ•ด๊ฒฐ ๋ฐฉ์•ˆ์„ ๊ณ ๋ฏผ ์ค‘

_ํŽธ์ง‘๋จ_
redux-observable์— ๋Œ€ํ•œ ๋‚ด ๋Œ€๋‹ต์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

|์ธก๋ฉด|Auth|TODO|
|---|---|---|
|์„œ๋ฒ„|true|์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค(์š”์ฒญ ์ฟ ํ‚ค ํ”„๋ก์‹œ ์‚ฌ์šฉ).|
|Server|false|๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ํ‘œ์‹œํ•˜๊ณ  ๋กœ๊ทธ์ธ ํ›„ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.|
|ํด๋ผ์ด์–ธํŠธ|true|์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.|
|Client|false|๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ํ‘œ์‹œํ•˜๊ณ  ๋กœ๊ทธ์ธ ํ›„ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. (ํŽ˜์ด์ง€ ๊ฐ„ ์ด๋™์—์„œ ์„ธ์…˜์ด ๋งŒ๋ฃŒ๋œ ๊ฒฝ์šฐ์—๋งŒ ๋ฐœ์ƒ)|

  static async getInitialProps({ store, query: { rowsPerPage, pageIndex }, req, auth }) {
    store.dispatch(TemporaryStoryActions.initPageState());

    const isAuthenticated = () => req ? req.isAuthenticated()
      : store.getState().auth.isAuthenticated;

    if (isAuthenticated()) {
      // fetch initial data
      const TemporaryStoryApiProxy = withCookieProxy(req, TemporaryStoryApi);
      await TemporaryStoryApiProxy.fetchTemporaryStories({
        rowsPerPage: rowsPerPage || 15,
        pageIndex: pageIndex || 0,                                                                                                                                  }).then(json => {
        store.dispatch(TemporaryStoryActions.loadTemporaryStories(
          json.rowsPerPage, json.pageIndex, json.count, json.rows));
      }).catch(error => {
        if (error.response && error.response.status === 403) {
          store.dispatch(AuthActions.initState(false, null));
          return;
        }
        throw error;
      });
    }

    if (!isAuthenticated()) {
      // => if client side fetch failed with 403, isAuthenticated() turns off to false
      // register logined action for client side login succeeded
      const reloadAction = TemporaryStoryActions.fetchTemporaryStories({
        rowsPerPage: rowsPerPage || 15,
        pageIndex: pageIndex || 0,
      });
      store.dispatch(AuthActions.addLoginedAction(reloadAction));
    }

    return {
      ...store.getState(),
    }                                                                                                                                                           }                                                                                             }
export const withLogin = Page => class SecurePage extends React.Component {
  static async getInitialProps (ctx) {
    if (ctx.req && ctx.store) {
      // server side
      const isAuthenticated = ctx.req.isAuthenticated();
      const { user } = ctx.req;
      ctx.store.dispatch(AuthActions.initState(isAuthenticated, user));
    }
    return Page.getInitialProps && await Page.getInitialProps(ctx)
  }

  render () {
    const { auth } = this.props;
    return auth.isAuthenticated ? <Page {...this.props} /> : <LoginPage />
  }                                                                                                                                                           }
// when [front-end server] => [api server]
// on Server Side Rendering,
// needs to proxy Cookies which sent to Next.js page request
export const withCookieProxy = (req, targetApi) => {
  if (!req) {
    return targetApi;
  }
  targetApi.client.interceptors.request.use(config => {
    const cookieString = Object.keys(req.cookies).map(key => `${key}=${req.cookies[key]}`).join('; ');
    const headers = {
      ...(config.headers || {}),
      Cookie: cookieString,
    };
    return {
      ...config,
      headers: headers,
    };
  }, error => {
    return Promise.reject(error);
  });
  return targetApi;
};
const loginEpic = (action$, state$) => action$.pipe(
  ofType(AuthActionTypes.login),
  mergeMap(action => {
    const email = action.payload.email;
    const password = action.payload.password;
    return from(AuthApi.login(email, password))
      .mergeMap(json => {
        const user = json.user;
        const loginedActions = state$.value.auth.loginedActions;
        const successActions = [
          AuthActions.removeAllLoginedActions(),
          ...loginedActions,
          AuthActions.loginSuccess(user.id, user.name, user.last_login_date),
        ];
        return from(successActions);
      }).pipe(catchError(error => {
        return of$(AuthActions.loginFail(error));
      }));
  }));

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ฐ„๋‹จํ•œ ์ž‘์—…์ด ์ˆ˜ํ–‰๋  ๋•Œ ๋ณต์žกํ•ด ๋ณด์ž…๋‹ˆ๋‹ค.

export const withAuthSync = WrappedComponent => class extends Component {
  componentDidMount() {
    window.addEventListener('storage', this.syncLogout);
  }

  componentWillUnmount() {
    window.removeEventListener('storage', this.syncLogout);
    window.localStorage.removeItem('logout');
  }

    syncLogout = (event) => {
      if (event.key === 'logout') {
        console.log('logged out from storage!');
        window.location.reload();
      }
    };

    render() {
      return <WrappedComponent {...this.props} />;
    }
};

์ด ์˜ˆ์ œ๋Š” Next.js 8์˜ ์ผ๋ถ€๋กœ ๋ณ‘ํ•ฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
https://github.com/zeit/next.js/tree/canary/examples/with-cookie-auth

@timneutkens ๋งํฌ ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

https://github.com/zeit/next.js/blob/canary/examples/with-cookie-auth/www/utils/auth.js#L26 -L34 ๋ฅผ ๋ณด๋ฉด ... ์ผ์ข…์˜ auth() ํ˜ธ์ถœ ํ›„ ํ™•์ธ?

์ฟ ํ‚ค ์—†์ด ์˜ˆ์ œ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋ฉด Profile.getInitialProps() ๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ๋ฐ˜๋ฉด ๋” ๋งŽ์€ "์ดˆ๊ธฐ ์†Œํ’ˆ"์„ ์–ป์œผ๋ ค๊ณ  ์‹œ๋„ํ•˜๊ธฐ ์ „์— ๋ฆฌ๋””๋ ‰์…˜์ด ๋ฐœ์ƒํ•  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค ...

์—ฌ๊ธฐ์—์„œ ์„œ๋ฒ„ ์ธก ์‚ฌ์ „ ๋ Œ๋”๋ง + ์ธ์ฆ w/ apollo๊ฐ€ ์žˆ๋Š” ์˜ˆ์ œ๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

https://github.com/HorizonShadow/apollo-next-now-test

OWASP ๋ณด์•ˆ ์ง€์นจ ์—์„œ๋Š” JWT ํ† ํฐ์„ ๋กœ์ปฌ ์ €์žฅ์†Œ์— ์ €์žฅํ•˜์ง€ ๋ง ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด "๋‹จ์ผ ๊ต์ฐจ ์‚ฌ์ดํŠธ ์Šคํฌ๋ฆฝํŒ…์„ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋Ÿฌํ•œ ๊ฐœ์ฒด์˜ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ํ›”์น  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ค‘์š”ํ•œ ์ •๋ณด๋ฅผ ๋กœ์ปฌ ์ €์žฅ์†Œ์— ์ €์žฅํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค."

์—ฌ๊ธฐ Auth0: ํ† ํฐ ์ €์žฅ ์œ„์น˜ ๋ฐ Tom Abbott: JWT ์ €์žฅ ์œ„์น˜ โ€“ ์ฟ ํ‚ค ๋Œ€ HTML5 ์›น ์ €์žฅ์†Œ .

๋‹ค์Œ์€ Nuxt.js + Express.js ํ”„๋ก์‹œ ์„œ๋ฒ„ + Django ๋ฐฑ์—”๋“œ๋ฅผ ์‚ฌ์šฉํ•œ ์˜ˆ์ž…๋‹ˆ๋‹ค. Express ์„œ๋ฒ„๊ฐ€ ์‹ค์ œ ๋ฐฑ์—”๋“œ์— ๋Œ€ํ•œ ์ธ์ฆ ์š”์ฒญ์„ ํ”„๋ก์‹œํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋ฉฐ ์ฟ ํ‚ค์— ์ €์žฅ๋˜๋Š” JWT ํ† ํฐ์„ ์‚ฌ์šฉํ•  ๋•Œ CSRF ๋ณดํ˜ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ(ํ† ํฐ์˜ ๊ธธ์ด/JWT ํ† ํฐ์— ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ์ •๋ณด์˜ ์–‘์— ๋Œ€ํ•œ ์ผ๋ถ€ ์ œํ•œ ๋ถ€๊ณผ): https:/ /github.com/danjac/nuxt-python-secure-example

@timneutkens ์ฟ ํ‚ค ๐Ÿช์—์„œ SSR ์‚ฌ์šฉ์ž ์ง€์ • redux ๋ฏธ๋“ค์›จ์–ด๋กœ ํ† ํฐ์„ ๋ณด๋‚ด๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ๋ฌธ์„œ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. _app.js ๋‚ด๋ถ€์— ์ฟ ํ‚ค๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ customApimiddleware์— ์–ด๋–ป๊ฒŒ ์ „๋‹ฌํ•ด์•ผํ•ฉ๋‹ˆ๊นŒ? ๊ฐ€์ ธ์˜ค๊ธฐ ์š”์ฒญ์„ ์ž‘์„ฑํ•œ ๊ณณ์ž…๋‹ˆ๋‹ค. ๊ฐ์‚ฌ ํ•ด์š”

Next.js ์ธ์ฆ/์‚ฌ์šฉ์ž ๊ณ„์ •์— ๋Œ€ํ•œ Medium ๊ธฐ์‚ฌ๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.
์ด๊ฒƒ์€ ๊ด‘๋ฒ”์œ„ํ•œ ํŠœํ† ๋ฆฌ์–ผ์ด์ž ๊ฑฐ์˜ 2๋…„ ๊ฐ„์˜ ์—ฌ๊ฐ€ ์‹œ๊ฐ„ ๊ฐœ๋ฐœ ๋ฐ ์‚ฌ๊ณ ์—์„œ ์–ป์€ ๋‚ด ๋ธŒ๋ ˆ์ธ ๋คํ”„์ž…๋‹ˆ๋‹ค(์ด ๋ฌธ์ œ์— ๋Œ€ํ•œ ์ฒซ ๋ฒˆ์งธ ๋…ผํ‰ ์€ 2017๋…„ 2์›”์— ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค).

https://medium.com/the-ideal-system/user-accounts-with-next-js-an-extensive-tutorial-6831cdaed16b

nextj.js ์•ฑ์—์„œ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์ตœ๊ณ ์˜ ํŠœํ† ๋ฆฌ์–ผ ์ค‘ ํ•˜๋‚˜๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๋‚˜๋Š” localStorage(XSS)์— ํ† ํฐ์„ ์ €์žฅํ•˜๋Š” ๊ฒƒ, ์ฟ ํ‚ค์— ํ† ํฐ์„ ์ €์žฅํ•˜๋Š” ๊ฒƒ(CSRF๋ฅผ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๊ณ ), ์‹ฌ์ง€์–ด ๋ธŒ๋ผ์šฐ์ €์˜ ์ฟ ํ‚ค์— ํ† ํฐ์„ ์ €์žฅํ•˜๋Š” ๊ฒƒ(XSS์™€ CSRF ๋ชจ๋‘ ์ทจ์•ฝํ•จ)๊ณผ ๊ฐ™์€ ๊ฒƒ์„ ๋ณด์•˜์Šต๋‹ˆ๋‹ค.

๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ๋‹ค๋ฅธ ์„œ๋น„์Šค ๊ฐ„์— ์„ธ์…˜ ์ •๋ณด๋ฅผ ๊ณต์œ ํ•˜๋Š” ์†”๋ฃจ์…˜์ด ์ •๋ง ๋งˆ์Œ์— ๋“ญ๋‹ˆ๋‹ค. next.js ์•ฑ์šฉ ์ปค์Šคํ…€ ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค๊ณ  ์‹ถ์ง€๋Š” ์•Š์ง€๋งŒ ์„ธ์…˜์„ ์ฒ˜๋ฆฌํ•˜๊ณ  csrf๋ฅผ ๋ฐฉ์ง€ํ•˜๋Š” ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค(์—ญ๋ฐฉํ–ฅ ํ”„๋ก์‹œ ์ถ”๊ฐ€). ๋ชจ๋†€๋ฆฌ์‹ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค(์•ฑ ๋ Œ๋”๋ง ๋ฐ db ์ž‘์—… ์ฒ˜๋ฆฌ ๋“ฑ).

์ผ๋ถ€ ์‚ฌ๋žŒ๋“ค(ZEIT ํฌํ•จ)์ด API๋ฅผ ์ƒํƒœ ๋น„์ €์žฅ ์ƒํƒœ๋กœ ์œ ์ง€ํ•˜๊ณ  next.js ์•ฑ์ด ์„ธ์…˜์„ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์„ ๋ณด์•˜์Šต๋‹ˆ๋‹ค. ํ† ํฐ์„ API์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์„ธ์…˜์„ ์ง„ํ–‰ํ•˜๋ฉด ์ƒํ™ฉ์ด ์กฐ๊ธˆ ๋” ๋นก๋นกํ•˜๊ณ  ๋œ ๋ณต์žกํ•ด์ง‘๋‹ˆ๋‹ค.

next.js์— ๋Œ€ํ•œ ์ „์ฒด ์ธ์ฆ ์˜ˆ์ œ๊ฐ€ ์žˆ์œผ๋ฉด ์ •๋ง ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์™ธ๋ถ€ API์— ๋Œ€ํ•œ ์ธ์ฆ, next.js ์•ฑ์— ์„ธ์…˜ ์œ ์ง€, ์„œ๋น„์Šค ๊ฐ„์— ์„ธ์…˜ ๊ณต์œ  ๋˜๋Š” ํ† ํฐ ์ „๋‹ฌ, ๋งŒ๋ฃŒ๋œ ํ† ํฐ ์ƒˆ๋กœ ๊ณ ์นจ ๋“ฑ์˜ ์ž‘์—…์ด ์žˆ์Šต๋‹ˆ๋‹ค. (๋งŽ์€ ์‚ฌ๋žŒ๋“ค์ด JWT์— ๋Œ€ํ•ด ๋งŽ์ด ์“ฐ๊ณ  ํŠœํ† ๋ฆฌ์–ผ์—์„œ ์‚ฌ์šฉํ•˜์ง€๋งŒ ๋Œ€๋ถ€๋ถ„ ๋งŒ๋ฃŒ๋˜๊ฑฐ๋‚˜ ์ƒˆ๋กœ ๊ณ ์ณ์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค.)

์–ด์จŒ๋“ , ๋‹น์‹ ์€ ์ด ์ฃผ์ œ์— ๋Œ€ํ•œ ๊ฐ€์žฅ ์™„๋ฒฝํ•œ ํŠœํ† ๋ฆฌ์–ผ ์ค‘ ํ•˜๋‚˜๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

ํ›จ์”ฌ ๋” ์™„์ „ํ•˜๊ณ  ๋ช…ํ™•ํ•œ ์˜ˆ์ œ์™€ ๋ฌธ์„œ๊ฐ€ ์žˆ๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

next.js์— ๋Œ€ํ•œ ์ „์ฒด ์ธ์ฆ ์˜ˆ์ œ๊ฐ€ ์žˆ์œผ๋ฉด ์ •๋ง ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์™ธ๋ถ€ API์— ๋Œ€ํ•œ ์ธ์ฆ, next.js ์•ฑ์— ์„ธ์…˜ ์œ ์ง€, ์„œ๋น„์Šค ๊ฐ„์— ์„ธ์…˜ ๊ณต์œ  ๋˜๋Š” ํ† ํฐ ์ „๋‹ฌ, ๋งŒ๋ฃŒ๋œ ํ† ํฐ ์ƒˆ๋กœ ๊ณ ์นจ ๋“ฑ์˜ ์ž‘์—…์ด ์žˆ์Šต๋‹ˆ๋‹ค. (๋งŽ์€ ์‚ฌ๋žŒ๋“ค์ด JWT์— ๋Œ€ํ•ด ๋งŽ์ด ์“ฐ๊ณ  ํŠœํ† ๋ฆฌ์–ผ์—์„œ ์‚ฌ์šฉํ•˜์ง€๋งŒ ๋Œ€๋ถ€๋ถ„ ๋งŒ๋ฃŒ๋˜๊ฑฐ๋‚˜ ์ƒˆ๋กœ ๊ณ ์ณ์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค.)

๋‚˜ ์—ญ์‹œ ์–ด๋–ค ์ ‘๊ทผ ๋ฐฉ์‹์„ ์„ ํƒํ•ด์•ผ ํ• ์ง€ ๋ง‰๋ง‰ํ•˜๋‹ค.
๊ธฐ์‚ฌ ๋งํฌ ์ •๋ง ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.
ํ˜„์žฌ ์–ด๋–ค ๊ตฌํ˜„์„ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๊นŒ?
๋‹ค์Œ v9.3+์— ๋Œ€ํ•œ ํฌ๊ด„์ ์ธ ์ธ์ฆ ์˜ˆ์ œ๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๊นŒ?

Auth0์˜ ์ƒˆ๋กœ์šด ์ฟ ํ‚ค ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ๋ฐฉ์‹์„ ํ™•์ธํ•ด ๋ณผ ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
(๋ฌผ๋ก  ์ด๊ฒƒ์€ ํŠน์ • ID ์ œ๊ณต์ž๋ฅผ ์œ„ํ•œ ๊ฒƒ์ด์ง€๋งŒ ์ ‘๊ทผ ๋ฐฉ์‹์ด ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)
https://github.com/auth0/nextjs-auth0

  • nextjs์˜ api ๊ฒฝ๋กœ(ํ•˜๋‚˜์˜ ๋™์  ๊ฒฝ๋กœ๋ฅผ ํ†ตํ•ด์„œ๋„)๋ฅผ ํ†ตํ•ด API ์š”์ฒญ์„ "ํ”„๋ก์‹œ"ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์€ ์ •๋ง ๋ฉ‹์ง„ ์ผ์ž…๋‹ˆ๋‹ค.
  • ๊ทธ๋Ÿฐ ๋‹ค์Œ ์•ก์„ธ์Šค ํ† ํฐ ๋“ฑ์„ ํด๋ผ์ด์–ธํŠธ ์ธก์— ๋…ธ์ถœํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค(nextjs API ๊ฒฝ๋กœ๋Š” ์„œ๋ฒ„ ์ธก๋งŒ ์‹คํ–‰). ์„œ๋ฒ„ ์ธก์—์„œ๋Š” auth0 ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ๋น„๋ฐ€์ด ์žˆ๋Š” ์ฟ ํ‚ค๋ฅผ ํ†ตํ•ด ์•ก์„ธ์Šค ํ† ํฐ ๋“ฑ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ ์ธก ์ฝ”๋“œ๋Š” nextjs API ๊ฒฝ๋กœ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  API ๊ฒฝ๋กœ๋Š” ์‹ค์ œ API ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

ReadMe์—์„œ ์ด ์ ‘๊ทผ ๋ฐฉ์‹์ด "์‹คํ—˜์ "์ด๋ผ๊ณ  ๋งํ•ฉ๋‹ˆ๋‹ค.

Auth0์˜ ์ƒˆ๋กœ์šด ์ฟ ํ‚ค ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ๋ฐฉ์‹์„ ํ™•์ธํ•ด ๋ณผ ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
(๋ฌผ๋ก  ์ด๊ฒƒ์€ ํŠน์ • ID ์ œ๊ณต์ž๋ฅผ ์œ„ํ•œ ๊ฒƒ์ด์ง€๋งŒ ์ ‘๊ทผ ๋ฐฉ์‹์ด ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)
https://github.com/auth0/nextjs-auth0

  • nextjs์˜ api ๊ฒฝ๋กœ(ํ•˜๋‚˜์˜ ๋™์  ๊ฒฝ๋กœ๋ฅผ ํ†ตํ•ด์„œ๋„)๋ฅผ ํ†ตํ•ด API ์š”์ฒญ์„ "ํ”„๋ก์‹œ"ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์€ ์ •๋ง ๋ฉ‹์ง„ ์ผ์ž…๋‹ˆ๋‹ค.
  • ๊ทธ๋Ÿฐ ๋‹ค์Œ ์•ก์„ธ์Šค ํ† ํฐ ๋“ฑ์„ ํด๋ผ์ด์–ธํŠธ ์ธก์— ๋…ธ์ถœํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค(nextjs API ๊ฒฝ๋กœ๋Š” ์„œ๋ฒ„ ์ธก๋งŒ ์‹คํ–‰). ์„œ๋ฒ„ ์ธก์—์„œ๋Š” auth0 ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ๋น„๋ฐ€์ด ์žˆ๋Š” ์ฟ ํ‚ค๋ฅผ ํ†ตํ•ด ์•ก์„ธ์Šค ํ† ํฐ ๋“ฑ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ ์ธก ์ฝ”๋“œ๋Š” nextjs API ๊ฒฝ๋กœ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  API ๊ฒฝ๋กœ๋Š” ์‹ค์ œ API ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

ReadMe์—์„œ ์ด ์ ‘๊ทผ ๋ฐฉ์‹์ด "์‹คํ—˜์ "์ด๋ผ๊ณ  ๋งํ•ฉ๋‹ˆ๋‹ค.

์ด ๊ธฐ์‚ฌ๋Š” ๋งค์šฐ ์œ ์šฉํ•˜๋ฉฐ ๋‹ค์–‘ํ•œ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋‹ค๋ฃน๋‹ˆ๋‹ค.
https://auth0.com/blog/ultimate-guide-nextjs-authentication-auth0/

API ๊ฒฝ๋กœ๋ฅผ ํ”„๋ก์‹œ๋กœ ์‚ฌ์šฉ, API ๊ฒฝ๋กœ๋ฅผ ํ†ตํ•œ ๋กœ๊ทธ์ธ/๋กœ๊ทธ์•„์›ƒ, API์—์„œ ํ† ํฐ ๊ฐ€์ ธ์˜ค๊ธฐ, HttpOnly ์ฟ ํ‚ค๋กœ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์€ ๊ฒฌ๊ณ ํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.
ํ•œ ๊ฐ€์ง€ ์šฐ๋ ค ์‚ฌํ•ญ์€ CSRF์ผ ์ˆ˜ ์žˆ์ง€๋งŒ csrf npm ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ผ๋ถ€ ์†”๋ฃจ์…˜์„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค( csurf ๊ฐ€ ์•„๋‹ˆ๋ผ ์ž‘๋™ํ•  ์ˆ˜๋„ ์žˆ์Œ).

@onderonur , auth0 ๊ธฐ์‚ฌ์— ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค.
์ฆ‰, ํ˜„์žฌ next.js๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ˆœ์ˆ˜ jwt์— ๋Œ€ํ•œ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๊ฑฐ๋‚˜ ์ตœ์†Œํ•œ์˜ ๊ตฌํ˜„ ์˜ˆ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?
์ฟ ํ‚ค๋กœ ๊ณ ๊ธ‰ ๋ ˆ์ด์–ด๋ฅผ ๋งŒ๋“ค๊ณ  ์„ค์ •ํ•˜๊ณ  ์‹ถ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. csr ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์šฐ๋ฆฌ๋Š” ๋‹จ์ˆœํžˆ ํ† ํฐ์„ localstorage์— ์ €์žฅํ•˜๊ณ  ์š”์ฒญ๊ณผ ํ•จ๊ป˜ ๋ณด๋ƒˆ์Šต๋‹ˆ๋‹ค.

@onderonur , auth0 ๊ธฐ์‚ฌ์— ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค.
์ฆ‰, ํ˜„์žฌ next.js๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ˆœ์ˆ˜ jwt์— ๋Œ€ํ•œ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๊ฑฐ๋‚˜ ์ตœ์†Œํ•œ์˜ ๊ตฌํ˜„ ์˜ˆ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?
์ฟ ํ‚ค๋กœ ๊ณ ๊ธ‰ ๋ ˆ์ด์–ด๋ฅผ ๋งŒ๋“ค๊ณ  ์„ค์ •ํ•˜๊ณ  ์‹ถ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. csr ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์šฐ๋ฆฌ๋Š” ๋‹จ์ˆœํžˆ ํ† ํฐ์„ localstorage์— ์ €์žฅํ•˜๊ณ  ์š”์ฒญ๊ณผ ํ•จ๊ป˜ ๋ณด๋ƒˆ์Šต๋‹ˆ๋‹ค.

๋‚ด ์ €์žฅ์†Œ ์ค‘ ํ•˜๋‚˜์— ์ด ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ ์•„์ง ์ดˆ์•ˆ์ด๋ฏ€๋กœ ์ง์ ‘ ํ…Œ์ŠคํŠธํ•˜์‹ญ์‹œ์˜ค. :)
https://github.com/onderonur/post-gallery
์‚ฌ์‹ค "์ฟ ํ‚ค ๋ ˆ์ด์–ด"๋Š” ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ์ด ์•„๋‹™๋‹ˆ๋‹ค. /api/login API ๊ฒฝ๋กœ๋ฅผ ํ†ตํ•ด API์˜ ๋กœ๊ทธ์ธ ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด httpOnly ์ฟ ํ‚ค์— ํ† ํฐ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
์ •ํ™•ํžˆ ๋™์ผํ•œ ๊ตฌํ˜„์— ๋Œ€ํ•ด ๋‚ด ์ €์žฅ์†Œ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค๋ฅธ ์˜ต์…˜์€ (๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์—์„œ ํ† ํฐ์„ ์„ค์ •ํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฑฐ์˜ ๋™์ผํ•œ ํ๋ฆ„์„ ์›ํ•  ๊ฒฝ์šฐ) js-cookie npm ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ ์ธก ์š”์ฒญ์œผ๋กœ ๋กœ๊ทธ์ธ ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ๋ฐ˜ํ™˜๋˜๋ฉด ์ข…๋ฃŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ† ํฐ, ์ฟ ํ‚ค์— ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์š”์ฒญ์„ ํ•  ๋•Œ(axios ์ธํ„ฐ์…‰ํ„ฐ ๋“ฑ์„ ํ†ตํ•ด) ์ฟ ํ‚ค ๊ฐ’์„ ์ฝ๊ณ  ์ด๋ฅผ ์š”์ฒญ ํ—ค๋”๋กœ API์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ด ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋Š” ๋งŽ์€(๊ทธ๋ฆฌ๊ณ  ์ผ๋ถ€ ์ธ๊ธฐ ์žˆ๋Š”) ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ๋ณด์•˜์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๊ฒƒ์€ ์•ฝ๊ฐ„ ์•ˆ์ „ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ €์—์„œ httpOnly ์ฟ ํ‚ค๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ JavaScript๋Š” ํ† ํฐ ์ฟ ํ‚ค๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ XSS ์ทจ์•ฝ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ ์˜ค๋ž˜๋œ ์Šค๋ ˆ๋“œ(์ผ๋ฐ˜์ ์œผ๋กœ ์˜ค๋ž˜ ์‹คํ–‰๋˜๋Š” ์ฃผ์ œ)์ด์ง€๋งŒ ์ถ”๊ฐ€ ์ฐธ์กฐ ๋˜๋Š” ์˜ˆ์ œ๋ฅผ ์ฐพ๋Š” ์‚ฌ๋žŒ๋“ค์„ ์œ„ํ•ด ์ตœ๊ทผ์— NextAuth.js v2์— ๋Œ€ํ•œ ์ž‘์—…์„ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” ๊ทธ๊ฒƒ์„ ํ”Œ๋Ÿฌ๊ทธ๋ผ๊ธฐ๋ณด๋‹ค๋Š” ์˜คํ”ˆ ์†Œ์Šค ํ”„๋กœ์ ํŠธ์ด๊ณ  ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์ด ๊ทธ๊ฒƒ์— ๋Œ€ํ•ด ๋„์›€์„ ์ฃผ์—ˆ๋‹ค๊ณ  ์–ธ๊ธ‰ํ•˜์ง€๋งŒ ์‚ฌ์šฉํ•˜๊ธฐ๊ฐ€ ๋งค์šฐ ๊ฐ„๋‹จํ•˜๊ณ  ์ฝ”๋“œ์™€ ์ ‘๊ทผ ๋ฐฉ์‹์€ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ์ฐธ์กฐ๋กœ ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

NextAuth v1๊ณผ ๊ฐ™์€ ์ผ๋ถ€ ๋ฐฐ๊ฒฝ์—์„œ๋Š” ์„œ๋ช…๋˜๊ณ  ์ ‘๋‘์‚ฌ๊ฐ€ ๋ถ™์€ HTTP ์ „์šฉ ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ ์ธก ํ† ํฐ์„ ์‚ฌ์šฉํ•˜๋Š” ์ผ๋ฐ˜์ ์ธ ๋ณด์•ˆ ์œ„ํ—˜์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

NextAuth.js v2๋Š” Apple, Google, Facebook, Twitter, GitHub, Auth0, Okta, Slack, Discord ๋ฐ ๊ธฐํƒ€ OAuth ๊ณต๊ธ‰์ž๋ฅผ ํ†ตํ•œ ๋กœ๊ทธ์ธ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค(1.x ๋ฐ 2.x ๋ชจ๋‘ ์ง€์›). MySQL, MariaDB, Postgres, MongoDB ๋˜๋Š” ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—†์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(100% ์„œ๋ฒ„๋ฆฌ์Šค ์†”๋ฃจ์…˜์˜ ๊ฒฝ์šฐ OAuth ๋ฐ JSON ์›น ํ† ํฐ๋งŒ ์‚ฌ์šฉ).

์‚ฌ์šฉ๋ฒ•์€ ๋งค์šฐ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. session() ๋ผ๋Š” ๋ฒ”์šฉ ์ •์  ๋ฉ”์„œ๋“œ์™€ ๊ตฌ์„ฑ ์š”์†Œ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” useSession() ๋ผ๋Š” React Hook์ด ์žˆ์Šต๋‹ˆ๋‹ค.

import { useSession } from 'next-auth/client'

export default () => {
  const [session, loading] = useSession()

  return <>
    {!loading && session && <p>Signed in as {session.user.name || session.user.email}.</p>}
    {!loading && !session && <p><a href="/api/auth/signin">Sign in here</a></p>}
  </>
}

Next.js 9.x ๋ฐ Serverless์šฉ์œผ๋กœ ๊ตฌ์ถ•๋˜์—ˆ์œผ๋ฉฐ Express ๋˜๋Š” PassportJS์™€ ๊ฐ™์€ ์ข…์† ํ•ญ๋ชฉ์ด ์—†์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—๋Š” _app.js ์—์„œ ๋ชจ๋“  ํŽ˜์ด์ง€์— ์ธ์ฆ ์ƒํƒœ๋ฅผ ์ž๋™์œผ๋กœ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ธ์ฆ ๊ณต๊ธ‰์ž๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ๋ฐ ์„œ๋ฒ„ ์ธก ๋ Œ๋”๋ง ๋ชจ๋‘์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

์ž์„ธํ•œ ๋‚ด์šฉ์€ next-auth.js.org๋ฅผ ์ฐธ์กฐํ•˜๊ฑฐ๋‚˜ NPM์˜ next-auth@beta ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

์•„์ง ์ง„ํ–‰ ์ค‘์ธ ์ž‘์—…์ž…๋‹ˆ๋‹ค. - ์šฐ๋ฆฌ๋Š” ์—ฌ์ „ํžˆ ๋ฌธ์„œ์™€ ์ด๋ฒคํŠธ ๋ชจ๋ธ์„ ๋‹ค๋“ฌ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค - ๋ชฉํ‘œ ๋ฆด๋ฆฌ์Šค ๋‚ ์งœ๋Š” 6์›” ์ดˆ~์ค‘์ˆœ์ž…๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ ์˜ค๋ž˜๋œ ์Šค๋ ˆ๋“œ(์ผ๋ฐ˜์ ์œผ๋กœ ์˜ค๋ž˜ ์‹คํ–‰๋˜๋Š” ์ฃผ์ œ)์ด์ง€๋งŒ ์ถ”๊ฐ€ ์ฐธ์กฐ ๋˜๋Š” ์˜ˆ์ œ๋ฅผ ์ฐพ๋Š” ์‚ฌ๋žŒ๋“ค์„ ์œ„ํ•ด ์ตœ๊ทผ์— NextAuth.js v2์— ๋Œ€ํ•œ ์ž‘์—…์„ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” ๊ทธ๊ฒƒ์„ ํ”Œ๋Ÿฌ๊ทธ๋ผ๊ธฐ๋ณด๋‹ค๋Š” ์˜คํ”ˆ ์†Œ์Šค ํ”„๋กœ์ ํŠธ์ด๊ณ  ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์ด ๊ทธ๊ฒƒ์— ๋Œ€ํ•ด ๋„์›€์„ ์ฃผ์—ˆ๋‹ค๊ณ  ์–ธ๊ธ‰ํ•˜์ง€๋งŒ ์‚ฌ์šฉํ•˜๊ธฐ๊ฐ€ ๋งค์šฐ ๊ฐ„๋‹จํ•˜๊ณ  ์ฝ”๋“œ์™€ ์ ‘๊ทผ ๋ฐฉ์‹์€ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ์ฐธ์กฐ๋กœ ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

NextAuth v1๊ณผ ๊ฐ™์€ ์ผ๋ถ€ ๋ฐฐ๊ฒฝ์—์„œ๋Š” ์„œ๋ช…๋˜๊ณ  ์ ‘๋‘์‚ฌ๊ฐ€ ๋ถ™์€ HTTP ์ „์šฉ ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ ์ธก ํ† ํฐ์„ ์‚ฌ์šฉํ•˜๋Š” ์ผ๋ฐ˜์ ์ธ ๋ณด์•ˆ ์œ„ํ—˜์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

NextAuth.js v2๋Š” Apple, Google, Facebook, Twitter, GitHub, Auth0, Okta, Slack, Discord ๋ฐ ๊ธฐํƒ€ OAuth ๊ณต๊ธ‰์ž๋ฅผ ํ†ตํ•œ ๋กœ๊ทธ์ธ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค(1.x ๋ฐ 2.x ๋ชจ๋‘ ์ง€์›). MySQL, MariaDB, Postgres, MongoDB ๋˜๋Š” ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—†์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(100% ์„œ๋ฒ„๋ฆฌ์Šค ์†”๋ฃจ์…˜์˜ ๊ฒฝ์šฐ OAuth ๋ฐ JSON ์›น ํ† ํฐ๋งŒ ์‚ฌ์šฉ).

์‚ฌ์šฉ๋ฒ•์€ ๋งค์šฐ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. session() ๋ผ๋Š” ๋ฒ”์šฉ ์ •์  ๋ฉ”์„œ๋“œ์™€ ๊ตฌ์„ฑ ์š”์†Œ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” useSession() ๋ผ๋Š” React Hook์ด ์žˆ์Šต๋‹ˆ๋‹ค.

import { useSession } from 'next-auth/client'

export default () => {
  const [session, loading] = useSession()

  return <>
    {!loading && session && <p>Signed in as {session.user.name || session.user.email}.</p>}
    {!loading && !session && <p><a href="/api/auth/signin">Sign in here</a></p>}
  </>
}

Next.js 9.x ๋ฐ Serverless์šฉ์œผ๋กœ ๊ตฌ์ถ•๋˜์—ˆ์œผ๋ฉฐ Express ๋˜๋Š” PassportJS์™€ ๊ฐ™์€ ์ข…์† ํ•ญ๋ชฉ์ด ์—†์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—๋Š” _app.js ์—์„œ ๋ชจ๋“  ํŽ˜์ด์ง€์— ์ธ์ฆ ์ƒํƒœ๋ฅผ ์ž๋™์œผ๋กœ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ธ์ฆ ๊ณต๊ธ‰์ž๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ๋ฐ ์„œ๋ฒ„ ์ธก ๋ Œ๋”๋ง ๋ชจ๋‘์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

์ž์„ธํ•œ ๋‚ด์šฉ์€ next-auth.js.org๋ฅผ ์ฐธ์กฐํ•˜๊ฑฐ๋‚˜ NPM์˜ next-auth@beta ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

์•„์ง ์ง„ํ–‰ ์ค‘์ธ ์ž‘์—…์ž…๋‹ˆ๋‹ค. - ์šฐ๋ฆฌ๋Š” ์—ฌ์ „ํžˆ ๋ฌธ์„œ์™€ ์ด๋ฒคํŠธ ๋ชจ๋ธ์„ ๋‹ค๋“ฌ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค - ๋ชฉํ‘œ ๋ฆด๋ฆฌ์Šค ๋‚ ์งœ๋Š” 6์›” ์ดˆ~์ค‘์ˆœ์ž…๋‹ˆ๋‹ค.

์ž˜ํ–ˆ์–ด!
ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? ์˜ˆ๋ฅผ ๋“ค์–ด Rails API ์•ฑ์ด ์žˆ๊ณ  ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ๋‹ค์Œ JS๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰