Статьи

Проверка подлинности Redux: защитите свое приложение с помощью Auth0

Эта статья была рецензирована Peleke Sengstacke . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!

Redux сейчас в моде в сообществе React и за его пределами, и на то есть веские причины. Это библиотека, созданная Дэном Абрамовым, которая обеспечивает рациональное управление однонаправленным потоком данных и позволяет разработчикам использовать мощные функции разработки, такие как путешествия во времени и запись / воспроизведение.

Звучит отлично, правда? Вот в чем суть: это происходит за счет необходимости писать больше кода. Однако, если у вас есть опыт поддержки больших приложений, вы можете знать, что обработка данных может стать громоздкой и сложной для управления. С Redux мы можем всегда иметь четкое представление о состоянии нашего приложения и точно знать, что делают наши данные.

В этом уроке мы рассмотрим, как начать создавать реальное приложение React + Redux, которое аутентифицирует пользователей и вызывает удаленный API для данных. Наше приложение получит список джедаев Star Wars из бэкэнда Node, чтобы мы могли отображать их имена и фотографии. Для аутентификации мы будем использовать Auth0, чтобы мы могли быстро приступить к работе, а также легко получить такие функции, как социальный вход в систему и многофакторная аутентификация .

один джедай

Мы не будем углубляться в элементарные концепции Redux, поэтому, если вы новичок в библиотеке, ознакомьтесь с некоторыми из этих замечательных ресурсов для начинающих:

Исходный код приложения, которое мы будем создавать, можно скачать здесь .

Проверка подлинности Redux: начало работы

Наш проект React для этого руководства будет написан на ES2015, поэтому мы будем использовать Babel для перехода к ES5 вместе с веб-пакетом для обработки связывания модулей. Вместо того, чтобы создавать вещи с нуля, почему бы нам не начать с реального стартового примера Дэна в репозитории Redux . Возьмите копию этого и установите зависимости.

npm install 

Подпишитесь на Auth0

Лучший способ выполнить аутентификацию для одностраничных приложений (наподобие того, которое мы создаем) — это использовать JSON Web Tokens (JWT). JWT предоставляют способ проверки подлинности без сохранения состояния с использованием API RESTful, и это дает много преимуществ по сравнению с сеансами и аутентификацией на основе файлов cookie. Недостатком является то, что использование нашего собственного решения для аутентификации JWT может быть сложным и подверженным ошибкам, но, к счастью, мы можем использовать Auth0 и не беспокоиться о деталях реализации сервера или безопасности.

Если вы еще этого не сделали, зайдите и зарегистрируйте бесплатную учетную запись Auth0. С помощью бесплатного плана мы получаем 7000 постоянных активных пользователей и можем использовать двух провайдеров социальных сетей.

После регистрации следуйте инструкциям для инициализации вашей учетной записи. Имейте в виду, что вы можете иметь несколько приложений под одной учетной записью, поэтому выберите доменное имя, которое подходит для вашей ситуации — возможно, название вашей организации. В качестве первого шага нам нужно установить localhost URL-адрес в качестве разрешенного источника. Это можно сделать в текстовой области «Allowed Origins (CORS)».

панель инструментов auth0

Настройте веб-сервер

Почему бы нам сначала не убрать веб-сервер джедаев? Это просто должен быть простой RESTful API, который возвращает наши Jedis в форме данных JSON, и быстрый способ сделать это с помощью NodeJS, использующей среду Express. Вы можете использовать любой серверный язык или инфраструктуру, которые вам нравятся, при условии, что возвращаются данные JSON.

Примечание : пуристы из «Звездных войн» заметят, что мы используем «джедай» в качестве формы множественного числа джедаев во всем приложении, но это не правильный множественный список. Скорее, мы должны просто использовать «джедай». Может быть и так, но я в порядке с этим, потому что это делает вещи проще в нашем приложении 🙂

Сначала инициализируйте приложение и установите зависимости:

 mkdir server && cd server touch server.js npm init npm install express express-jwt cors 

Мы можем предоставить весь код, который понадобится нашему серверу, в одном файле JavaScript.

 // server.js const express = require('express'); const app = express(); const jwt = require('express-jwt'); const cors = require('cors'); app.use(cors()); app.use(express.static('public')); // Authentication middleware provided by express-jwt. // This middleware will check incoming requests for a valid // JWT on any routes that it is applied to. const authCheck = jwt({ secret: 'AUTH0_SECRET', // If your Auth0 client was created before Dec 6, 2016, // uncomment the line below and remove the line above // secret: new Buffer('AUTH0_SECRET', 'base64'), audience: 'AUTH0_CLIENT_ID' }); var jedis = [ { id: 1, name: 'Luke Skywalker', image: 'http://localhost:7000/images/luke-skywalker.jpg' }, { id: 2, name: 'Anakin Skywalker', image: 'http://localhost:7000/images/anakin-skywalker.png' }, { id: 3, name: 'Yoda', image: 'http://localhost:7000/images/yoda.png' }, { id: 4, name: 'Obi-Wan Kenobi', image: 'http://localhost:7000/images/obi-wan-kenobi.jpg' }, { id: 5, name: 'Mace Windu', image: 'http://localhost:7000/images/mace-windu.jpg' } ]; app.get('/api/jedis', (req, res) => { const allJedis = jedis.map(jedi => { return { id: jedi.id, name: jedi.name } }); res.json(allJedis); }); app.get('/api/jedis/:id', authCheck, (req, res) => { res.json(jedis.filter(jedi => jedi.id === parseInt(req.params.id))[0]); }); app.listen(7000); console.log('Listening on http://localhost:7000'); 

У нас есть массив джедаев и две конечные точки для их обработки. Первая конечная точка возвращает всех джедаев, но только их свойства id и name . Вторая конечная точка в /jedis/:id возвращает одного джедая, но также включает в себя и URL изображения. Эта вторая конечная точка — та, которую мы собираемся защищать с помощью нашего промежуточного программного обеспечения для аутентификации, и оно ограничено только аутентифицированными пользователями.

Но как мы на самом деле защищаем эту конечную точку? Мы используем express-jwt для создания промежуточного программного обеспечения, которое ищет входящий веб -токен JSON и проверяет его по секретному ключу, который мы предоставляем. Затем мы можем применить это промежуточное программное обеспечение к любой из наших конечных точек — что мы делаем со вторым аргументом к конечной точке /jedis/:id — и только те запросы, которые содержат действительный токен с ними, будут пропущены.

Само промежуточное ПО настраивается путем предоставления нашего authCheck секретного ключа Auth0 и идентификатора authCheck , и именно здесь вы можете предоставить ключи, специфичные для вашего приложения. Эти ключи можно найти на панели управления Auth0 в приложениях .

Образы джедаев приходят из public каталога на сервере. Вы можете получить те же изображения из репозитория или включить ссылки на изображения из других источников в данные, если хотите.

С сервером на месте, давайте проверим, что API работает должным образом. Мы можем сделать это с помощью такого инструмента, как Почтальон .

почтальон

Если мы пойдем по маршруту /api/jedis , мы сможем получить полный список джедаев, как и ожидалось. Однако, если мы попытаемся получить одного джедая, нам не разрешат вернуть ресурс, потому что мы не отправляем токен на сервер.

почтальон неверный токен

Мы увидим, как отправлять токены с нашими запросами, как только мы реализуем наши вызовы API в самом приложении, но, по сути, все, что нам нужно сделать, это включить его в заголовок Authorization используя схему Bearer .

Настройка Redux

Auth0Lock

Учетные записи Auth0 поставляются с потрясающим предварительно созданным виджетом под названием Lock, который значительно упрощает процесс фактического входа в приложение. Мы можем получить JavaScript, необходимый для этого виджета, из CDN Auth0.

 <!-- index.html --> <!-- Auth0Lock script --> <script src="//cdn.auth0.com/js/lock-9.min.js"></script> <!-- Setting the right viewport --> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> 

Маршрутизация

Мы собираемся ограничить наше приложение одним маршрутом для краткости. Когда речь заходит о маршрутизации, существуют некоторые проблемы с аутентификацией, такие как ограничение определенных маршрутов только теми пользователями, которые проходят аутентификацию, но это тема для будущей статьи. А пока давайте все упростим и определим единый маршрут.

 // routes.js import React from 'react' import { Route } from 'react-router' import App from './containers/App' export default ( <Route path="/" component={App}></Route> ) 

Это дает нам базовый маршрут, который использует компонент под названием App .

Настройка действий

Редукторы — это сердце Redux, и они дают нам чистый и предсказуемый способ изменить состояние нашего приложения. При использовании Редукторов мы должны убедиться, что никакие данные не видоизменяются. Это дает нам возможность проверять каждое предыдущее состояние данных в нашем приложении, и это важная концепция в Redux.

Хотя редукторы — это главное, нам все еще нужны действия, чтобы все происходило в приложении. Давайте добавим все действия, которые нам понадобятся для нашего приложения Jedis.

 // actions/index.js import { CALL_API } from '../middleware/api' export const LOGIN_SUCCESS = 'LOGIN_SUCCESS' export const LOGIN_ERROR = 'LOGIN_ERROR' function loginSuccess(profile) { return { type: LOGIN_SUCCESS, profile } } function loginError(err) { return { type: LOGIN_ERROR, err } } export function login() { const lock = new Auth0Lock('AUTH0_CLIENT_ID', 'AUTH0_DOMAIN') return dispatch => { lock.show((err, profile, token) => { if(err) { return dispatch(loginError(err)) } localStorage.setItem('profile', JSON.stringify(profile)) localStorage.setItem('id_token', token) return dispatch(loginSuccess(profile)) }) } } export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS' function logoutSuccess(profile) { return { type: LOGOUT_SUCCESS } } export function logout() { return dispatch => { localStorage.removeItem('id_token'); localStorage.removeItem('profile'); return dispatch(logoutSuccess()); } } export const JEDIS_REQUEST = 'JEDIS_REQUEST' export const JEDIS_SUCCESS = 'JEDIS_SUCCESS' export const JEDIS_FAILURE = 'JEDIS_FAILURE' function fetchJedis() { return { [CALL_API]: { types: [ JEDIS_REQUEST, JEDIS_SUCCESS, JEDIS_FAILURE ], endpoint: 'jedis', authenticatedRequest: false } } } export function loadJedis() { return dispatch => { return dispatch(fetchJedis()) } } export const JEDI_REQUEST = 'JEDI_REQUEST' export const JEDI_SUCCESS = 'JEDI_SUCCESS' export const JEDI_FAILURE = 'JEDI_FAILURE' function fetchJedi(id) { return { [CALL_API]: { types: [ JEDI_REQUEST, JEDI_SUCCESS, JEDI_FAILURE ], endpoint: `jedis/${id}`, authenticatedRequest: true } } } export function loadJedi(id) { return dispatch => { return dispatch(fetchJedi(id)) } } 

Константные значения, которые мы экспортируем, — это «типы действий», которые мы хотим прослушивать в наших компонентах, что мы увидим позже. Когда мы имеем дело с запросами к API, мы обычно хотим иметь три типа действий: один, чтобы сигнализировать о том, что мы сделали запрос, другой, который обрабатывает случай успеха, и, наконец, один, который обрабатывает случай ошибки. Это именно то, что мы делаем с нашими вызовами API, и мы настраиваем некоторые функции, основанные на замечательной функции, которую Redux допускает: промежуточное программное обеспечение.

Благодаря промежуточному программному обеспечению в Redux у нас есть много возможностей, включая такие вещи, как ведение журнала . Мы также можем использовать промежуточное программное обеспечение для обработки наших реальных вызовов API, и мы полагаемся на это здесь в наших действиях, чтобы выполнять запросы к нашему API. Скоро мы увидим полный код промежуточного программного обеспечения API.

Действия Redux полагаются на то, что мы используем dispatch чтобы запустить их в действие, и это то, что мы делаем во всех наших экспортируемых функциях действий. Идея состоит в том, что мы можем отправлять действие, основанное на некотором событии в наших компонентах React, и тип действия, связанный с этим действием, заставит редуктор реагировать соответствующим образом. Именно этот ответ говорит нашему приложению, что его часть должна быть обновлена.

Стоит отметить, что действия, которые мы настроили для блокировки, имеют только два типа: один для успеха, другой для отказа. Почему не тип действия, когда открывается замок и когда делается запрос? Все сводится к тому, нужно ли нам на самом деле знать об этих действиях. Что мы получаем, зная, что виджет блокировки открыт? Возможно, где-то есть сценарий использования, но для нашего приложения нам просто нужно знать, прошел ли пользователь успешную аутентификацию или нет.

Функция login — это место, где происходит настоящая магия аутентификации Auth0. Виджет блокировки дает нам метод show который фактически делает виджет видимым. Как только мы введем наши учетные данные и нажмем «Отправить», обратный вызов обрабатывает то, что будет дальше. Если по какой-либо причине возникает ошибка, мы dispatch тип ошибки вместе с сообщением.

Однако если все получится, мы сохраняем JWT и объект профиля пользователя, которые возвращаются из ответа, в локальном хранилище, чтобы их можно было вызывать и использовать позже в приложении. Мы также dispatch с типом успеха, если это происходит, и пропускаем профиль, чтобы при необходимости его можно было использовать в редукторе.

Теперь, когда мы экспортируем некоторые функции действий, мы можем использовать их в наших компонентах. Однако прежде чем мы сможем это сделать, нам нужно создать промежуточное ПО API, которое мы используем с CALL_API .

Создайте промежуточное ПО API

Как упоминалось ранее, промежуточное ПО Redux открывает множество возможностей в наших приложениях. Они также являются идеальным механизмом для обработки вызовов API. Мы можем создать одно общее промежуточное программное обеспечение API, а затем использовать его в любых наших действиях для выполнения вызовов нашего бэкэнда. Мы будем использовать Fetch для выполнения вызовов API и предоставления опций для отправки как аутентифицированных, так и неаутентифицированных запросов.

 // middleware/api.js export const API_ROOT = 'http://localhost:7000/api/' function callApi(endpoint, authenticatedRequest) { let token = localStorage.getItem('id_token') || null let config = {} if(authenticatedRequest) { if(token) { config = { headers: { 'Authorization': `Bearer ${token}` } } } else { throw new Error("No token saved!") } } return fetch(API_ROOT + endpoint, config) .then(response => response.json() .then(resource => ({ resource, response })) ).then(({ resource, response }) => { if (!response.ok) { return Promise.reject(resource) } return resource }) } export const CALL_API = Symbol('Call API') export default store => next => action => { const callAPI = action[CALL_API] if (typeof callAPI === 'undefined') { return next(action) } let { endpoint, types, authenticatedRequest } = callAPI if (typeof endpoint !== 'string') { throw new Error('Specify a string endpoint URL.') } if (!Array.isArray(types) || types.length !== 3) { throw new Error('Expected an array of three action types.') } if (!types.every(type => typeof type === 'string')) { throw new Error('Expected action types to be strings.') } function actionWith(data) { const finalAction = Object.assign({}, action, data) delete finalAction[CALL_API] return finalAction } const [ requestType, successType, failureType ] = types next(actionWith({ type: requestType })) return callApi(endpoint, authenticatedRequest).then( response => next(actionWith({ response, authenticatedRequest, type: successType })), error => next(actionWith({ type: failureType, error: error.message || 'Error!' })) ) } 

Начиная сверху, мы определяем нашу базовую конечную точку API, а затем создаем функцию для выполнения вызовов API. Эта функция принимает аргументы как для конечной точки, к которой должен быть отправлен запрос, так и для логического значения того, должен ли запрос быть аутентифицирован. Если это должен быть защищенный запрос, мы устанавливаем заголовок Authorization который содержит JWT пользователя из локального хранилища.

Как мы видели в наших действиях, когда мы используем символ CALL_API для запуска промежуточного программного обеспечения, мы предоставляем массив всех трех наших типов действий для ключа types . Эти типы действий выбираются в действиях, чтобы их можно было передать нашему редуктору. Как и следовало ожидать, successType используется в случае успеха в методе callApi функции callApi , а errorType используется в случае ошибки.

Реализуйте редукторы

Мы находимся на последнем этапе, прежде чем сможем реально использовать Redux в наших компонентах React! Нам нужно реализовать редуктор Redux, чтобы фактически реагировать на наши действия и возвращать данные неизменным образом. Нам нужно проверить срок действия JWT пользователя, и мы можем легко это сделать с помощью библиотеки jwt-decode из Auth0, поэтому давайте установим это сейчас.

 npm install jwt-decode 

С этим на месте, давайте создадим редуктор.

 // reducers/index.js import * as ActionTypes from '../actions' import { routerReducer as routing } from 'react-router-redux' import { combineReducers } from 'redux' const jwtDecode = require('jwt-decode') function checkTokenExpiry() { let jwt = localStorage.getItem('id_token') if(jwt) { let jwtExp = jwtDecode(jwt).exp; let expiryDate = new Date(0); expiryDate.setUTCSeconds(jwtExp); if(new Date() < expiryDate) { return true; } } return false; } function getProfile() { return JSON.parse(localStorage.getItem('profile')); } function auth(state = { isAuthenticated: checkTokenExpiry(), profile: getProfile(), error: '' }, action) { switch (action.type) { case ActionTypes.LOGIN_SUCCESS: return Object.assign({}, state, { isAuthenticated: true, profile: action.profile, error: '' }) case ActionTypes.LOGIN_ERROR: return Object.assign({}, state, { isAuthenticated: false, profile: null, error: action.error }) case ActionTypes.LOGOUT_SUCCESS: return Object.assign({}, state, { isAuthenticated: false, profile: null }) default: return state } } function jedis(state = { isFetching: false, allJedis: [], error: '' }, action) { switch (action.type) { case ActionTypes.JEDIS_REQUEST: return Object.assign({}, state, { isFetching: true }) case ActionTypes.JEDIS_SUCCESS: return Object.assign({}, state, { isFetching: false, allJedis: action.response, error: '' }) case ActionTypes.JEDIS_FAILURE: return Object.assign({}, state, { isFetching: false, allJedis: [], error: action.error }) default: return state } } function jedi(state = { isFetching: false, singleJedi: {}, error: '' }, action) { switch (action.type) { case ActionTypes.JEDI_REQUEST: return Object.assign({}, state, { isFetching: true }) case ActionTypes.JEDI_SUCCESS: return Object.assign({}, state, { isFetching: false, singleJedi: action.response, error: '' }) case ActionTypes.JEDI_FAILURE: return Object.assign({}, state, { isFetching: false, singleJedi: {}, error: action.error }) default: return state } } const rootReducer = combineReducers({ routing, auth, jedis, jedi }) export default rootReducer 

Редуктор по существу определяет начальное состояние, а затем возвращает новое состояние на основе действия. Вновь возвращенное состояние должно быть новым объектом, а не просто измененной версией исходного состояния. Вот почему мы используем Object.assign и Object.assign пустой объект в качестве первого аргумента; это дает нам гарантию, что новое государство будет уникальным.

auth редуктор начинается с проверки, истек ли пользовательский JWT или нет. Это логическое значение isAuthenticated будет использоваться во всем приложении для условного скрытия и отображения различных элементов, которые зависят от состояния аутентификации пользователя.

Когда пользователь успешно входит в систему, его состояние isAuthenticated устанавливается isAuthenticated true, а для его профиля также устанавливается объект профиля, полученный из Auth0. Этот объект профиля имеет такие свойства, как picture , nickname и некоторые другие, которые полезны для отображения области профиля.

Редукторы для наших запросов Jedi имеют логическое значение isFetching , а также данные, поступающие от действий.

Наконец, мы объединяем все эти отдельные редукторы в один rootReducer с помощью combineReducers . Теперь давайте перейдем к реальному коду приложения!

Создать компонент приложения

Первым из наших компонентов будет компонент корневого контейнера под названием App .

 // containers/App.js import React, { Component, PropTypes } from 'react' import { connect } from 'react-redux' import { loadJedis, loadJedi, login, logout } from '../actions' import JedisList from '../components/JedisList' import Jedi from '../components/Jedi' import Auth from '../components/Auth' class App extends Component { constructor(props) { super(props) this.handleGetJedisClick = this.handleGetJedisClick.bind(this) this.handleGetJediClick = this.handleGetJediClick.bind(this) this.handleLoginClick = this.handleLoginClick.bind(this) this.handleLogoutClick = this.handleLogoutClick.bind(this) } handleGetJedisClick() { this.props.loadJedis() } handleGetJediClick(id) { this.props.loadJedi(id) } handleLoginClick() { this.props.login() } handleLogoutClick() { this.props.logout() } render() { const { allJedis, singleJedi, error, isAuthenticated, profile } = this.props return ( <div> <div className="navbar navbar-default"> <div className="container-fluid"> <a className="navbar-brand">Redux Jedi</a> <Auth isAuthenticated={isAuthenticated} profile={profile} onLoginClick={this.handleLoginClick} onLogoutClick={this.handleLogoutClick} /> </div> </div> <div className="container-fluid"> <JedisList jedis={allJedis} error={error} onClick={this.handleGetJedisClick} onGetJediClick={this.handleGetJediClick} isAuthenticated={isAuthenticated} /> <Jedi jedi={singleJedi} /> </div> </div> ) } } function mapStateToProps(state) { const { jedis, jedi, auth } = state const { allJedis, error } = jedis const { singleJedi } = jedi const { isAuthenticated, profile } = auth return { allJedis, singleJedi, error, isAuthenticated, profile } } export default connect(mapStateToProps, { loadJedis, loadJedi, login, logout })(App) 

Большая часть того, что здесь происходит, довольно стандартная React: мы добавляем пару других компонентов, которые мы создадим в следующем разделе, и мы передаем им реквизиты. Переданные нами реквизиты включают нашу аутентификацию и данные Jedis, а также некоторые функции-обработчики, которые будут использоваться для реагирования на события щелчка.

При использовании Redux отличается тем, что нам нужно предоставить карту состояний и объект наших действий для функции connect . Эта функция по существу соединяет наш компонент React с хранилищем Redux, которое находится в каталоге store . Когда мы предоставляем функцию mapStateToProps , компонент будет обновлять данные всякий раз, когда они изменяются в результате отправки наших действий.

Давайте теперь создадим другие компоненты, которые нам понадобятся.

Создайте компонент Auth

Компонент Auth будет отображать кнопки «Вход» и «Выход», а также фотографию профиля пользователя и псевдоним.

 // components/Auth.js import React, { Component, PropTypes } from 'react' export default class Auth extends Component { constructor(props) { super(props) } render() { const { onLoginClick, onLogoutClick, isAuthenticated, profile } = this.props return ( <div style={{ marginTop: '10px' }}> { !isAuthenticated ? ( <ul className="list-inline"> <li><button className="btn btn-primary" onClick={onLoginClick}>Login</button></li> </ul> ) : ( <ul className="list-inline"> <li><img src={profile.picture} height="40px" /></li> <li><span>Welcome, {profile.nickname}</span></li> <li><button className="btn btn-primary" onClick={onLogoutClick}>Logout</button></li> </ul> )} </div> ) } } 

Теперь, когда пользователи нажимают кнопку «Войти», появляется виджет блокировки Auth0.

виджет блокировки

После успешного входа в систему, изображение профиля пользователя и никнейм отображаются на панели навигации вместе с кнопкой «Выйти».

профиль auth0

Теперь, когда мы можем войти в приложение, давайте JedisList компоненты Jedi и JedisList .

Создайте компоненты Jedi и JedisList

Мы хотим, чтобы на боковой панели были перечислены все наши джедаи.

 // components/JediList.js import React, { Component, PropTypes } from 'react' function getJediListItem(jedi, isAuthenticated, onClick) { return( <li key={jedi.id} className="list-group-item"> { isAuthenticated ? ( <a onClick={() => onClick(jedi.id)}> <h4 style={{ cursor: 'pointer' }}>{jedi.name}</h4> </a> ) : ( <h4>{jedi.name}</h4> )} </li> ) } export default class JedisList extends Component { constructor(props) { super(props) } render() { const { jedis, error, onClick, onGetJediClick, isAuthenticated } = this.props const jedisList = jedis.map(jedi => getJediListItem(jedi, isAuthenticated, onGetJediClick)) return ( <div className="col-sm-3"> <button className="btn btn-primary" onClick={onClick} style={{ marginBottom: '10px' }}>Get Jedis</button> { jedis && <ul className="list-group"> {jedisList} </ul> } { error && <span className="text-danger">{error}</span> } </div> ) } } 

Когда мы нажимаем кнопку «Get Jedis», loadJedis наша функция loadJedis которая отправляет действие для отправки запроса на сервер.

список джедаев

HTML- onGetJediClick который мы создаем для нашего списка джедаев, включает в себя обработчик кликов, который использует onGetJediClick для извлечения отдельного джедая. Поскольку мы защищаем конечную точку, которая возвращает отдельные Jedis, с помощью промежуточного программного обеспечения для аутентификации, мы не хотим делать их кликабельными, если пользователь не аутентифицирован, поэтому мы проверяем значение isAuthenticated чтобы условно вернуть список без кликов.

Теперь нам просто нужен компонент Jedi .

 // components/Jedi.js import React, { Component, PropTypes } from 'react' export default class Jedi extends Component { render() { const { jedi } = this.props return ( <div className="col-sm-9"> { jedi && <div> <h2>{jedi.name}</h2> <img src={jedi.image} /> </div> } </div> ) } } 

Этот компонент просто отображает имя и изображение отдельного джедая.

один джедай

Завершение

Использование Redux определенно означает, что нам нужно написать больше кода для достижения цели, но это может быть справедливой ценой, чтобы сделать наш поток данных предсказуемым, что является ключевым моментом, когда наши приложения начинают расти. Как уже упоминалось в статье, использование Redux также дает нам возможность использовать такие интересные функции, как путешествия во времени и запись / воспроизведение.

Аутентификация может быть сложной для любого одностраничного приложения, но Auth0 позволяет легко приступить к работе с ней быстро. Использование Auth0 также означает, что мы можем мгновенно реализовать такие функции, как социальный вход в систему и многофакторная аутентификация.