Как реализовать авторизацию пользователя на стеке React Redux Node?

Не могу понять как правильно реализовать авторизацию, а именно не пойму саму схему такой авторизации. Поделитесь опытом заранее благодарен!
  • Вопрос задан
  • 13735 просмотров
Решения вопроса 1
maxfarseer
@maxfarseer
https://maxpfrontend.ru, обучаю реакту и компании
Пригодится 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.js

export 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.js
export 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-запросы устанавливаете заголовок с токеном
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы