์ ํจ๊ป:
now.sh
์์ ํธ์คํ
๋๋ ๊ฐ๋จํ ์ํธ ์๋ ์ด๋ฉ์ผ ๋ฐฑ์๋๋ง์ ์๋ด๊ธฐ๋ค์๊ฒ ํฐ ๋์์ด ๋ ๊ฒ ๊ฐ์ต๋๋ค.
์ ์: 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 ์ด๊ฒ์ ๊ฒฐ๊ณผ์ ๋๋ค.
@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
์ฟ ํค ์ ๊ทผ ๋ฐฉ์์ ์ ๋๋ก ์ํ๋๋ฉด ๋งค์ฐ ์์ ํ ์ ์์ต๋๋ค. ๋ค์์ ์ํํ๋ ๊ฒ์ ๋งค์ฐ ๊ฐ๋จํฉ๋๋ค.
๋ํ ์๋ฒ์์ ์ง์ ์ธ์ฆ ์ ๋ณด์ ์ก์ธ์คํ ์ ์๋ ๊ฒฝ์ฐ ๋๊ธฐ ์๊ฐ์ ์ด์ ์ด ๋งค์ฐ ๋์ต๋๋ค.
์ด ์์ ๋ฅผ 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 ์์ ์ ์ฌ๊ถ ํตํฉ์ด ๊ฐ๋ฅํฉ๋๋ค. ์ฌ๋๋ค์ด ๊ตฌ์ฑ ์์์ ์ธ์ ์ ๋ณด๋ฅผ ์ ํ/๋ ธ์ถํ๋ ๋ ๋์ ๋ฐฉ๋ฒ์ ๋ํ ์ข์ ์์ด๋์ด๊ฐ ์๋ค๋ฉด ์ ๋ ๋งค์ฐ ๊ด์ฌ์ด ์์ต๋๋ค.
์ ๋ง ๋๋ผ์ด @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/
๋ฐฑ์๋:
@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 ' ์์ ๋ฅผ ์ ์ฉํด
๋ด ์๊ตฌ ์ฌํญ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๋๊ตฐ๊ฐ๊ฐ ์ฌ๊ธฐ์ ๋์์ด๋๋ ๊ฒฝ์ฐ ์๋ฒ ์ธก ์ธ์ฆ์ด์๋ ๋ด ์ฑ์ ๋๋ค ๐
์ธ์ฆ์ Next.js ์๋ฒ์์ ๋ถ๋ฆฌ๋์ด์ผ ํ์ง๋ง ๋ค๋ฅธ ์ฌ๋์ด ์ด์ ๋ํด ์๊ฐ์ ์ฃผ๊ธฐ๋ฅผ ๊ธฐ๋ค๋ฆฌ๊ณ ์์ต๋๋ค... ๋ํ CSRF๋ก๋ถํฐ ์ ๋๋ก ๋ณดํธ๋๋์ง ํ์คํ์ง ์์ต๋๋ค.
์ด๊ฒ์ด ๋ด๊ฐ ํ ์ผ์ ๋๋ค.
์ด๊ฒ์ 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 ๋ฅผ ์ ๋ฐ์ดํธํ์ต๋๋ค.
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์ด ํฐ ๋์์ด ๋์์ต๋๋ค.
๋ด ์๋ฃจ์ ์ ๋ค์์ ์ํํฉ๋๋ค.
๋์ปค ์ปจํ
์ด๋์์ ์คํ ์ค์ธ ์์ฒด 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์ ๋ํด ์ธ์ฆํ ์ ์๊ธฐ๋ฅผ ์ํฉ๋๋ค. ์ด ์๋ฃจ์ ์ด ์ค์ ๋ก ๋์์ด ๋์ง ์๋๋ค๊ณ ์๊ฐํฉ๋๋ค.
ํ๋ฆ์ ์๊ฐํ์ต๋๋ค.
์ํฐํฐ:
๋ชฉํ๋ 3๊ฐ์ ์ฟ ํค ๊ธฐ๋ฐ ์ธ์ ์ ์ค์ ํ๋ ๊ฒ์ ๋๋ค.
Session 1)์ Client๊ฐ Server๋ฅผ ์ธ์ํ๊ธฐ ์ํ ๊ฒ์ด๊ณ , 2) 3)์ Client์ Server๊ฐ ๊ฐ๊ฐ์ ์ธ์ ์ ์ฌ์ฉํ์ฌ API์ ๋ ๋ฆฝ์ ์ผ๋ก ์ ๊ทผํ ์ ์๋๋ก ํ๋ ๊ฒ์ ๋๋ค.
๋ค์์ ํ๋ฆ์ ๋๋ค.
๋น์ ์ ์๊ฒฌ์ ์ด๋ป์ต๋๊น? ๋ ์ฌ์ด ๋ฐฉ๋ฒ์ด ์์ต๋๊น? ํ๋์ ์ธ์ (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
๋์์ด ๋ ์ ์์ต๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ๋ ๊ฐ์ง ์ต์ ์ด ์์ต๋๋ค.
๋ค์ ์๋ฒ์ JWT๋ฅผ ์ ๊ณตํฉ๋๋ค(์ฟ ํค๋ฅผ ํตํด ๊ฐ๋ฅ). ์ด๋ ๊ฒ ํ๋ฉด Next ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ๋ฅผ ๋์ ํ์ฌ API๋ฅผ ํธ์ถํ ์ ์์ต๋๋ค. ์ ์ฒด ์๋ฒ ์ธก ๋ ๋๋ง์ด ์ค์ํ๋ค๋ฉด ์ด๊ฒ์ ์ํ ๊ฒ์ ๋๋ค.
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๋ก ์ธ์ฆ์ ์๋ ํ์ต๋๊น?
๋ชจ๋๋ค ์๋ ! ์ง๋ ๋ช ๊ฐ์ ๋์ ๋ด๊ฐ ๋ง๋ค๊ณ ์ด์ ์ธ๋ จ๋๊ฒ ๋ง๋ค์์ต๋๋ค.
ํน์ง:
์ฌ๊ธฐ์์ ๋ผ์ด๋ธ ๋ฐ๋ชจ๋ฅผ ํ์ธํ์ญ์์ค: 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 ์ฑ๋์์ ์ด๋์ฅ์ ๋ฐ์ ์ ์์ต๋๋ค.
@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
@nmaro ์ฌ์์ https://github.com/malixsys/mobazoo/blob/master/pages/profile.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 ํด๋์ ๊ณต์ ๋ก์ปฌ ์ธ์ฆ ์์ ๊ฐ ์ถ๊ฐ๋๋ ๊ฒ์ ๋ณผ ์ ์์๊น์? ์๋๋ฉด ์ฐจํ ๋ฆด๋ฆฌ์ค๋ฅผ ์ํด ์์ง ์์ ์ค์ธ ๊ฒ์ ๋๊น?
Ooth๋ ์ด์ next.js ์ธ์ฆ์ ์ธ๋ถ ์ฌํญ์ ํฌํจ ๋ฅผ ๊ฐ๊ฒ ๋์์ต๋๋ค .
@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 ์ฑํ ์์ ๋์์ ์ป์๊ณ ์ด๋ฅผ ์ฌ๋ฌ๋ถ ๋ชจ๋์ ๊ณต์ ํ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ์ต๋๋ค. ๋ชจ๋ ํผ๋๋ฐฑ์ ํญ์ ๊ฐ์ฌํฉ๋๋ค.
์๋
์๋ค์!
๋ค์์ ๋ด๊ฐ ๋ช ๋ฌ ์ ์ ๊ตฌ์ถํ next.js๋ฅผ ์ฌ์ฉํ ์ธ์ฆ์ ๋ ๋ค๋ฅธ ์์
๋๋ค. ๋๊ตฐ๊ฐ ์ ์ฉํ๋ค๊ณ ์๊ฐํ ๊ฒ์
๋๋ค.
Ooth 2.0 ์ ์๋กญ๊ณ ๋ ๋์ ๋ฌธ์ ์ ํจ๊ป ์ถ์๋์์ต๋๋ค.
Ooth๋ node.js์ฉ์ผ๋ก ๊ตฌ์ถ๋ ์ฌ์ฉ์ ID ๊ด๋ฆฌ ์์คํ ์ ๋๋ค(next.js๋ฅผ ์ผ๋์ ๋๊ณ ).
ํ์ฌ ์ง์๋๋ ์ ๋ต:
next.js( ์์ค ์ฝ๋ )์ ํจ๊ป ๋ชจ๋ ๊ฒ์ ํตํฉํ๋ ์ด ๋ผ์ด๋ธ ์์ ๋ฅผ ํ์ธํ์ญ์์ค.
์ฌ๊ธฐ์ ์๋ ๋ง์ ์ธ์ฆ ์ํ(๋ฐ examples ํด๋์ ์์)์ getInitialProps
์์ ์ฌ์ฉ์ session/token/etc ๋ฅผ ๋ฐํํฉ๋๋ค. ๋ด๊ฐ ์ดํดํ๋ ํ ํ์ด์ง๊ฐ ์๋ฒ ์ธก์์ ๋ ๋๋ง๋๋ฉด ์ฌ์ฉ์ ์ธ์
์ ๋ณด๊ฐ HTML ํ์ด์ง ์๋ต์ ์ผ๋ถ๋ก( NEXT_DATA
์ผ๋ถ๋ก) ๋ธ๋ผ์ฐ์ ๋ก ์ ์ก๋ฉ๋๋ค.
getInitialProps
๊ฐ ์๋ฒ ์ธก์์ ์คํ๋ ๋ ์ด ํจํด์ ๋ ๊ฐ์ง ๋ณด์ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
1) ์ฌ์ฉ์ ์ธ์
์ ๋คํธ์ํฌ๋ฅผ ํตํด ์๋ฒ์์ ๋ธ๋ผ์ฐ์ ๋ก ์ ์ก๋ฉ๋๋ค. ๊ทธ๋ฌํ ์์ฒญ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ http://
ํ์ง๋ฅผ https://
, ์ฌ์ฉ์์ ํ ํฐ ๋ฑ์ ์ผ๋ฐ ํ
์คํธ๋ก ๋คํธ์ํฌ๋ฅผ ํตํด ๋
ธ์ถ ๋ ๊ฒ์ด๋ค.
2) ์ฌ์ฉ์ ์ธ์
์ HTML ํ์ด์ง์ ์ผ๋ถ๋ก ์๋ฒ์์ ๋ธ๋ผ์ฐ์ ๋ก ๋ฐํ๋ฉ๋๋ค( NEXT_DATA
์คํฌ๋ฆฝํธ ํ๊ทธ์์). ์ฌ์ฉ์์ ํ ํฐ/๋ฑ์ HTML ํ์ด์ง์ ์ง์ ๋ฐฐ์นํ๋ ๊ฒ์ ์ํํด ๋ณด์
๋๋ค. ํนํ ํ์ด์ง๊ฐ ๊ตฌ๋ฌธ ๋ถ์๋๊ณ ๋ธ๋ผ์ฐ์ ์์ ๋ ๋๋ง๋๊ณ ๋ค๋ฅธ ํ์ฌ ์คํฌ๋ฆฝํธ๊ฐ ์ด์ ์คํ ์ค์ผ ์ ์๋ ๊ฒฝ์ฐ์๋ ๋์ฑ ๊ทธ๋ ์ต๋๋ค.
์ด๋ฌํ ๋ฌธ์ ๋ ์ด๋ฏธ ํด๊ฒฐ๋์์ต๋๊น? ์ด๋ฌํ ์ํ์ ๋ํ ์ํ ๋ฐฉ๋ฒ์ด ์์ต๋๊น?
๊ทธ๋์ ์ฟ ํค๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ฌ๊ธฐ์์ ๋ด ์๋ฅผ ์ฐธ์กฐ ํ์ญ์์ค.
๋ง์นจ๋ด! ์ฌ๊ธฐ์์ ๋ค์ ์ปจํ ์ด๋๊ฐ ํฌํจ๋ ์์ ํ ๊ณ ์ ๋ next.js ์ธ์ฆ ์์ ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.
๋ชจ๋ ๊ฒ์ด 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๊ฐ ์ค์ ๋ก ์ํ ๋น์ ์ฅ์ด๋ผ๋ ๋ค๋ฅธ ์ด์ ์ ์ ๊ฑฐํฉ๋๋ค.
์ด๊ฒ์ ์ํฉ์ ๋ํ ๋์ ์ดํด์ ๋๋ค.
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 ์ธ์ฆ ์ ๋ต์ ์ฌ์ฉํ ์ ์์ต๋๋ค(๊ตต๊ฒ ํ์๋ ์ ํญ๋ชฉ).
@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๊ฐ ์๋ ์์ ๋ฅผ ๋ง๋ค์์ต๋๋ค.
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
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 ๋ฅผ ํ์ธํ์ธ์.
npm i 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 ๋ฅผ ํ์ธํ์ธ์.
- ํํ์ด์ง: https://next-auth.js.org
- ๋ฐ๋ชจ: https://next-auth-example.now.sh
- NPM:
npm i next-auth@beta
์์ง ์งํ ์ค์ธ ์์ ์ ๋๋ค. - ์ฐ๋ฆฌ๋ ์ฌ์ ํ ๋ฌธ์์ ์ด๋ฒคํธ ๋ชจ๋ธ์ ๋ค๋ฌ๊ณ ์์ต๋๋ค - ๋ชฉํ ๋ฆด๋ฆฌ์ค ๋ ์ง๋ 6์ ์ด~์ค์์ ๋๋ค.
์ํ์ด!
ํด๋ผ์ด์ธํธ ์ธก์์๋ง ์ฌ์ฉํ ์ ์์ต๋๊น? ์๋ฅผ ๋ค์ด Rails API ์ฑ์ด ์๊ณ ํด๋ผ์ด์ธํธ ์ธก์์ ๋ค์ JS๋ฅผ ์ฌ์ฉํฉ๋๋ค.
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
๊ทธ๋์ ๋๋ auth๊ฐ ์์์ ํ๊ณ ์์ต๋๋ค. ๋ค๋ฅธ ๊ณณ์์ ์ธ๊ธํ๋ฏ์ด ํด๋ผ์ด์ธํธ ์ธก ์ ์ฉ์ด๋ฉฐ ๊ถ๊ทน์ ์ผ๋ก ์ ํฌ์ ์ ๋ฐ์ ๋ถ๊ณผํฉ๋๋ค.
"๋งค์ฐ ์์ ํ"
php์ ๋ง์ฐฌ๊ฐ์ง๋ก Next์ ์์ ๋จ์๋ ํ์ด์ง์ ๋๋ค. ๊ฐ์ฅ ๋ฉ์ง ๊ธฐ๋ฅ ์ค ํ๋๋ ์์ฒญ๋ ๊ฒฝ์ฐ์๋ง ๊ฐ ํ์ด์ง๋ฅผ ์ง์ฐ ๋ก๋ํ๋ค๋ ๊ฒ์ ๋๋ค. ํด๋ผ์ด์ธํธ ์ธก ์ธ์ฆ๋ง ์์ง๋ง ์๋ฒ ๋ ๋๋ง์ ์ฌ์ฉํ๋ฉด ๋ณดํธ๋ ํ์ด์ง์ js๊ฐ ์ค์ ๋ก ๋ธ๋ผ์ฐ์ ์์ ๋ค์ด๋ก๋๋ฉ๋๋ค. ์์ผ๋ก Next๊ฐ ์๋ฒ ์ํฌํ๋ก๋ฅผ ์ถ๊ฐํ๋ฉด ์๋ฒ์์ ๋ ๋๋ง ๋ฐ ๋ฆฌ๋๋ ์ ์ ์ฐจ๋จํ์ฌ ์ด๋ฅผ ์์ ํ ๋ฐฉ์งํ ์ ์๊ธฐ๋ฅผ ๋ฐ๋๋๋ค. ์ฌ๊ธฐ์๋ ์ฟ ํค, ์ธ์ ๋ฐ AFAIK ์ธ์ ์ ์ฅ์๊ฐ ํ์ํ์ง๋ง ์ด๋ ์ด๋ฌํ ํ์ด๋ธ๋ฆฌ๋ ์ฑ์ ์ํํ๋ ๋ฐ ๋๋ ๋น์ฉ์ผ ๋ฟ์ ๋๋ค.
์ธ์ฆ ์
/token
๋ฐ/me
๋ ๊ฐ์ ๊ด์ฌ ์๋ํฌ์ธํธ๊ฐ ์๋ JWT ๋ณด์ API๊ฐ ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค./token
๋ ์ด๋ฉ์ผ/๋น๋ฐ๋ฒํธ ์๊ฒฉ ์ฆ๋ช ์ ์๋ฝํ๊ณ ์๋ช ๋ JWT(id_token
)๋ฅผ ๋ฐํํ๋ ๋ฐ๋ฉด/me
๋ JWT ์ธ์ฆ ์ฌ์ฉ์์ ๊ด๋ จ๋ ํ๋กํ ์ ๋ณด๋ฅผ ๋ฐํํฉ๋๋ค. Auth0์ ์ ๊ธ์์ ๋ค์AuthService.js
์ ์ ์ฉํ์ต๋๋ค(์ต์ ์ ์์ด๋์ด๋ ์๋์ง๋ง ์ด๋ฒคํธ ์ด๋ฏธํฐ ์ ๊ฑฐ). ๊ฑฐ์ ๋ชจ๋ JWT ํ ํฐ ์ฒ๋ฆฌ๋ฅผ ์ถ์ถํ๋ฏ๋ก ๋ก๊ทธ์ธ ํ์ด์ง์ ๊ณ ์ฐจ ๊ตฌ์ฑ ์์(๋์ค์ ์์ธํ ์ค๋ช )์์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.๋ค์์ ํ์ด์ง ๋ณดํธ๋ฅผ ๋ ๊ฐ๋จํ๊ฒ ๋ง๋๋ HOC์ ๋๋ค. ๋ฏผ๊ฐํ ์ ๋ณด์ ์์น ์๋ ํ๋์๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ํ์ด์ง๋ ์ฒซ ๋ฒ์งธ ๋ ๋๋ง์์
Loading...
๋ฅผ ์๋ฒ ๋ ๋๋งํ๋ ๋์ ๋ฐ์์ด ๋ถํ ๋๊ณ localStorage์์ ํ ํฐ์ ์ฝ์ต๋๋ค. ์ด๋ ๋ณดํธ๋ ํ์ด์ง๊ฐ SEO๊ฐ ์๋์ ์๋ฏธํฉ๋๋ค. ์ด๋ ํ์ฌ๋ก์๋ ๊ด์ฐฎ์ง๋ง ํ์คํ ์ต์ ์ ์๋๋๋ค.๋ก๊ทธ์ธ์ด ๊ณต๊ฐ๋์ด์ผ ํ๊ธฐ ๋๋ฌธ์ ๋ก๊ทธ์ธ ํ์ด์ง๋ ํ์ฌ HOC๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ AuthService์ ์ธ์คํด์ค๋ฅผ ์ง์ ๋ง๋ญ๋๋ค. ๊ฐ์ ํ์ด์ง์์๋ ๋น์ทํ ์์ ์ ์ํํฉ๋๋ค.
Airbnb์ react-with-styles์์ ์๊ฐ์ ๋ฐ์ ํ์ด์ง์์ ์ฌ์ฉํ HOC๋ฅผ ๋ฐํํ๋ ํจ์๊ฐ ๋
next-with-auth
lib ์์ ๋ ์์ํ์ต๋๋ค. ๋๋ ๋ํAuthService
์ ์ด HOC๋ฅผ ๋ณํฉํ์ฌ ํ๋ ์ดํ์ต๋๋ค. ํ ๊ฐ์ง ํด๊ฒฐ์ฑ ์ ์ด HOC๊ฐ redux connect์ ๊ฐ์ ๊ตฌ์ฑ ์์ ์ธ์ ์ธ์๋ก ๊ถํ ์์ค ๊ธฐ๋ฅ์ ํ์ฉํ๋๋ก ํ๋ ๊ฒ์ ๋๋ค. ์ด์จ๋ ๋ด ์๊ฐ์๋next-with-auth
๊ณผ ๊ฐ์ด ์ฌ์ฉํฉ๋๋ค.Redux๋ก ์ด ๋ชจ๋ ์์ ์ ์ํํ๋ ๊ฒ์ ๋ถํ์ํ๊ฒ ๋ณต์กํด ๋ณด์ด์ง๋ง ๊ธฐ๋ณธ์ ์ผ๋ก wiki ์์ ๋ฅผ ๋ฐ๋ฅผ ์ ์์ง๋ง AuthService๋ฅผ Actions(๋ก๊ทธ์ธ ๋ฐ ๋ก๊ทธ์์)๋ก ์ด๋ํ๊ณ User Reducer๊ฐ ์์ต๋๋ค. ์๋ฒ์ localStorage๊ฐ ์๊ธฐ ๋๋ฌธ์ ํด๋ผ์ด์ธํธ์์๋ง ์ด๋ฌํ ์์ ์ ํธ์ถํ ์ ์์ผ๋ฏ๋ก ์์ ์์ ์ด๋ฅผ ํ์ธํด์ผ ํฉ๋๋ค. ๊ฒฐ๊ตญ redux store๋
window
๋ฉ๋๋ค. ๋ฐ๋ผ์ ์ปจํ ์คํธ๋ฅผ ์ฌ์ฉํ๋ ๋์window
์ ์ฌ์ฉ์๋ฅผ ์ง์ ์บ์ํ ์ ์์ต๋๋ค. redux๋ฅผ ์ํ์ง ์๋๋ค๋ฉดreact-broadcast
์ฌ์ฉํด ๋ณผ ์๋ ์์ต๋๋ค.๋ง์ง๋ง์ผ๋ก
next/server
๊ฐ #25์ ๋ฐ๋ผ ๋ฐฐ์ก๋๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.next-with-auth
๋ ๋ฏธ๋ค์จ์ด + HOC๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ๋ฐ์๋ก๋ถํฐ ๋ณต์กํ localStorage ๋ ์ฟ ํค ํญ๋ชฉ์ ์ถ์ํํ ์ ์์ต๋๋ค. ํ ํฐ ์๋ก ๊ณ ์นจ๋ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.