V
V
Valery Serov2017-01-11 21:29:47
JavaScript
Valery Serov, 2017-01-11 21:29:47

How to implement user authorization on React Redux Node stack?

I can’t understand how to correctly implement authorization, namely, I don’t understand the very scheme of such authorization. Share your experience thanks in advance!

Answer the question

In order to leave comments, you need to log in

1 answer(s)
M
Maxim, 2017-01-11
@DrBronson

jwt will come in handy. ( google )
In general terms: you need to give the user a token. With this token, the user will make requests to your protected api methods (you can leave some methods "without a token").
Login route example (not done in the best way, in terms of callbacks, but what was at hand...):

...
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;

An example of a secure route (a method that requires a token):
...
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) => {
        // ... необходимые действия в базе, в процессе успешного зачтения голоса...
    }
})

Client application :
Typical ActionCreator (you can take bluebird , isomorphic-fetch , or at least native xhr as a request library)
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 })
      })
  }
}

An example of voting with a token (above, in the node.js part there was an api method, and this is a PUT request from the client)
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 })
      })
  }
}

I hope the code of the form with the "vote" button does not work.
====
All this can be organized more conveniently, for example, automatically setting a token for all requests (we are talking about the client part):
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(... код оработки запроса ...)
}

===
You can check the existence of the token in the router using the hook on onEnter .
...
<Provider store={store}>
      <Router history={routerHistory}>
        {configRoutes(store)} // Роуты создаются функцией, чтобы можно было использовать внутри нее store.getState()
      </Router>
    </Provider>
...

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>
  )

It remains only to create an AuthenticatedContainer in which to check: is there a currentUser (within this example) and if not, return false. And if there is - this.props.children (in which there will be further routes ..)
===
Total :
1) you have an API written in node.js.
2) This API has protected methods that check for the presence of a token in an http request. The token is issued upon login request. Of course, the login operation (that is, a POST request to your-api/login) does not require a token.
3) After a successful login, you receive a token in response to your request (this is already inside the client code, and if we are talking about redux, then inside an asynchronous call in actions)
4) Save the token (for example, in localStorage)
5) In all necessary http requests, set the header with the token

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question