Пригодится jwt. (
гугл)
В общих словах: вам нужно выдать юзеру токен. С этим токеном юзер будет выполнять запросы к вашим защищенным методам api (вы можете оставить какие-то методы "без токена").
Node.js часть
Пример роута на логин (сделано не лучшим образом, в плане коллбэков, но что нашлось под рукой...):
auth.js...
const v4 = require('node-uuid').v4
const jwt = require('jsonwebtoken')
...
router.post('/signin', (req, res, next) => {
// валидация, например...
if (errors) {
return res.status(400).json({ errors })
} else {
// поиск юзера в базе, сравнение хэша и прочие необходимые операции
...
// допустим все ок, нужно ответить токеном
// генерируем необходимые опции и сам токен
const payload = {
_id: user._id,
iss: 'http://localhost:3000',
permissions: 'poll',
}
const options = {
expiresIn: '7d',
jwtid: v4(),
}
const secret = new Buffer(process.env.AUTH0_CLIENT_SECRET, 'base64')
jwt.sign(payload, secret, options, (err, token) => {
// отвечаем токеном, для будущих запросов с клиента
return res.json({ data: token })
})
...
})
module.exports = router;
Пример защищенного роута (метода, который требует токен):
...
const jwt = require('jsonwebtoken')
const Poll = require('../models/poll')
...
const requireToken = (req,res,next) => {
const token = req.headers['x-api-key']
const secret = new Buffer(process.env.AUTH0_CLIENT_SECRET, 'base64')
jwt.verify(token, secret, (err, decoded) => {
if (err) {
return res.status(401).json({ error: err.message })
}
req.params.userId = decoded._id
next()
})
}
...
// обратите внимание на requireToken - функция вызывается при каждом обращении к роуту
// то есть при каждом PUT запросе по адресу (например: PUT api/v1/products/1238914)
// будет вызываться requireToken
router.put('/:id', requireToken, (req, res, next) => {
const { productId } = req.body
const userId = req.params.userId
Poll.findById(req.params.id)
.populate({ path: 'products' })
.exec((err, poll) => {
// ... необходимые действия в базе, в процессе успешного зачтения голоса...
}
})
Клиентское приложение:
Типичный ActionCreator (в качестве библиотеки для запросов можно взять
bluebird,
isomorphic-fetch, да хоть нативный xhr)
actions/LoginActions.jsexport function login(data) {
return dispatch => {
dispatch({ type: LOGIN_REQUEST })
// request в данном случае - https://github.com/KyleAMathews/superagent-bluebird-promise
return request.post(`${API_ROOT_V1}/auth/signin`)
.send(data)
.then(res => {
if (!res.ok) {
dispatch({ type: LOGIN_FAILURE })
} else {
dispatch({
type: LOGIN_SUCCESS,
data: res.body.data,
})
//сохранение токена в localStorage
localStorage.setItem('cks_token', res.body.data)
}
}, err => {
dispatch({ type: LOGIN_FAILURE })
})
}
}
Пример голосования с токеном (выше, в node.js части был метод апи, а это PUT запрос с клиента)
export function vote(productId, voteId) {
return dispatch => {
dispatch({ type: POLL_VOTE_REQUEST })
return request.put(`${API_ROOT_V1}/api/v1/vote/${voteId}`)
.set('X-API-Key', localStorage.getItem('cks_token')) // установка ТОКЕНА в заголовок 'X-API-Key'
.send({ productId })
.then(res => {
if (!res.ok) {
dispatch({ type: POLL_VOTE_FAILURE })
} else {
dispatch({
type: POLL_VOTE_SUCCESS,
data: normalize(res.body.data, schema.poll),
})
}
}, err => {
dispatch({ type: POLL_VOTE_FAILURE })
})
}
}
Надеюсь код формы с кнопкой "голосовать" не трубется.
====
Все это можно организовать удобнее, например, выставлять токен всем запросам автоматически (речь про клиентскую часть):
import fetch from 'isomorphic-fetch'
...
const defaultHeaders = {
Accept: 'application/json',
'Content-Type': 'application/json',
}
function buildHeaders() {
const authToken = localStorage.getItem('TOKEN')
return { ...defaultHeaders, Authorization: authToken }
}
...
export function httpPost(url, data) {
const body = JSON.stringify(data)
return fetch(url, {
method: 'post',
headers: buildHeaders(),
body: body,
})
.then(... код оработки запроса ...)
}
===
Проверить наличие токена, можно с в роутере, с помощью хука на
onEnter.
root.js...
<Provider store={store}>
<Router history={routerHistory}>
{configRoutes(store)} // Роуты создаются функцией, чтобы можно было использовать внутри нее store.getState()
</Router>
</Provider>
...
routes.jsexport default function configRoutes(store) {
function _ensureAuthenticated(nextState, replace, callback) {
const { dispatch } = store
const { session } = store.getState()
const { currentUser } = session
let nextUrl
if (!currentUser && localStorage.getItem('cks.token')) {
dispatch(getCurrentAccount())
} else if (!localStorage.getItem('cks.token')) {
replace('/signin')
}
callback()
}
return (
<Route path='/' component={App}>
<Route path='/signin' component={SigninContainer} />
<Route path='/signup' component={SignupContainer} />
<Route path='/locked' component={AuthenticatedContainer} onEnter={_ensureAuthenticated}>
<Route component={LockedArea}>
<Route path='/locked/a' component={A} />
<Route path='/locked/b/:id' component={B} />
<Route path='/locked/c' component={C} />
</Route>
</Route>
</Route>
)
Остается только создать
AuthenticatedContainer в котором проверять: есть ли currentUser (в рамках этого примера) и если нет - возвращать false. А если есть - this.props.children (в которых будут дальнейшие роуты..)
===
Итого:
1) у вас есть API написанное на node.js.
2) У этого API есть защищенные методы, которые проверяют наличия токена в http-запросе. Токен выдается при запросе на логин. Само собой, операция логина (то есть POST запрос на your-api/login) не требует токена.
3) После удачного логина, вы получаете в ответе на свой запрос токен (это уже внутри клиентского кода, и если мы говорим о redux - то внутри асинхронного вызова в actions)
4) Сохраняете токен (например, в localStorage)
5) Во все необходимые http-запросы устанавливаете заголовок с токеном