Статьи

Регистрация пользователей с помощью Node, React и Okta

Эта статья была первоначально опубликована в блоге разработчиков Okta . Спасибо за поддержку партнеров, которые делают возможным использование SitePoint.

Сегодняшние интернет-пользователи ожидают персонализированного опыта. Разработчики должны научиться разрабатывать веб-сайты, которые предоставляют этот персонализированный опыт, сохраняя при этом конфиденциальность информации их пользователей. Современные веб-приложения также обычно имеют серверный API и пользовательский интерфейс на стороне клиента. это может быть сложным, чтобы заставить оба конца знать о текущем вошедшем в систему пользователе. В этом руководстве я расскажу вам о настройке Node API, поддерживающей интерфейс React, и о регистрации пользователя, которая хранит личную и личную информацию пользователя.

В этом руководстве я не буду использовать библиотеки управления состоянием, такие как Redux или ReduxThunk. В более надежном приложении вы, вероятно, захотите это сделать, но вам будет легко подключить Redux и ReduxThunk, а затем добавить операторы fetch используемые здесь как ваши thunk. Для простоты, и чтобы эта статья была сосредоточена на добавлении управления пользователями, я буду добавлять операторы выборки в функции componentDidMount .

Установите узел и отреагируйте на предварительные условия

Чтобы настроить базовое приложение, убедитесь, что у вас установлены следующие основные инструменты:

  • Узел (8+)
  • нпм (5+)
  • создать-реакция-приложение (пакет npm)
  • экспресс-генератор (пакет npm)

Вам также понадобится аккаунт разработчика Okta .

Чтобы установить Node и npm, вы можете следовать инструкциям для вашей операционной системы по адресу https://nodejs.org/en/ .

Затем просто установите два пакета npm с помощью командной строки npm:

 npm i -g create-react-app express-generator 

Теперь вы готовы настроить базовую структуру приложения.

Эшафот Базовое Приложение

Перейдите в папку, в которой вы хотите разместить приложение, и создайте для него новую папку:

 mkdir MembershipSample cd MembershipSample express api create-react-app client 

Это создаст две папки в папке MembershipSample именем api и client , с приложением NodeJS и Express в папке api и базовым приложением React в папке client . Таким образом, ваша структура папок будет выглядеть так:

  • MembershipSample
    • апи
    • клиент

Чтобы упростить эту следующую часть, откройте две клеммы или клеммы клемм; один — в папку api экспресс-приложения, а другой — в client приложения React.

По умолчанию приложение React и приложение Node будут работать на порте 3000 в процессе разработки, поэтому вам нужно настроить API для запуска на другом порту и затем проксировать его в клиентском приложении.

В папке api откройте файл /bin/www и измените порт, на котором будет работать API, на 3001 .

 /** * Get port from environment and store in Express. */ var port = normalizePort(process.env.PORT || '3001'); app.set('port', port); 

Затем настройте прокси-сервер для API в клиентском приложении, чтобы вы могли по-прежнему вызывать /api/{resource} и пересылать его с порта 3000 на порт 3001. В файле client/package.json добавьте параметр proxy под name :

 "name": "client", "proxy": "http://localhost:3001" 

Наконец, не забудьте запустить npm install или yarn install для каждой подпапки ( api и client ), чтобы убедиться, что зависимости установлены.

Теперь вы можете запустить оба приложения, запустив npm start или yarn start в соответствующих папках для API и клиентского приложения.

Добавить приложение Okta

Если вы еще этого не сделали, создайте бесплатную учетную запись для разработчиков на https://developer.okta.com/signup/ .

После того, как вы зарегистрировались, нажмите Приложения в верхнем меню. Затем нажмите кнопку Добавить приложение .

экран списка приложений

Затем вы перейдете к мастеру создания приложений. Выберите кнопку « Одностраничное приложение» и нажмите « Далее» внизу.

одностраничный экран приложения

На следующем экране вы увидите настройки по умолчанию, предоставленные одностраничным шаблоном приложения. Измените название приложения на более описательное, например «Заявка на членство». Кроме того, измените базовые URI и параметры URI перенаправления входа в систему, чтобы использовать порт 3000, потому что именно там будет работать ваше приложение. Остальные настройки по умолчанию в порядке.

экран настроек одностраничного приложения

Затем нажмите кнопку Готово внизу.

После создания приложения выберите его в списке приложений и нажмите на вкладку « Общие », чтобы просмотреть общие настройки приложения.

вкладка общих настроек

Внизу вы увидите настройку идентификатора клиента (очевидно, ваша не будет размыта). Скопируйте это, чтобы использовать в вашем приложении React. Вам также потребуется URL-адрес организации Okta, который вы можете найти в левом верхнем углу страницы панели инструментов. Вероятно, это будет выглядеть примерно так: «https://dev-XXXXXX.oktapreview.com».

Добавить аутентификацию в приложение ReactJS

Теперь, когда приложение создано, добавьте аутентификацию с помощью Okta, добавив пару зависимостей npm. Из папки client запустите:

 npm install @okta/okta-react react-router-dom --save 

Или, если вы используете менеджер пакетов пряжи :

 yarn add @okta/okta-react react-router-dom 

Добавьте в папку client/src app.config.js именем app.config.js . Содержимое файла:

 export default { url: '{yourOktaDomain}', issuer: '{yourOktaOrgUrl}/oauth2/default', redirect_uri: window.location.origin + '/implicit/callback', client_id: '{yourClientID}' } 

Затем настройте файл index.js для использования React Router и Okta React SDK. Когда файл index.js будет завершен, он будет выглядеть так:

 import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter as Router } from 'react-router-dom'; import { Security } from '@okta/okta-react'; import './index.css'; import config from './app.config'; import App from './App'; import registerServiceWorker from './registerServiceWorker'; function onAuthRequired({ history }) { history.push('/login'); } ReactDOM.render( <Router> <Security issuer={config.issuer} client_id={config.client_id} redirect_uri={config.redirect_uri} onAuthRequired={onAuthRequired}> <App /> </Security> </Router>, document.getElementById('root') ); registerServiceWorker(); 

По завершении вы BrowserRouter компонент BrowserRouter (также BrowserRouter «Маршрутизатор») из React Router и компонент Security из Okta React SDK. Кроме того, файл app.config.js импортируется как «config», так что вы можете использовать значения config в свойствах, требуемых компонентом Security .

Вы также окружите компонент App компонентами Router и Security , передав им указанные значения. Метод onAuthRequired просто сообщает React SDK от Okta о том, что, когда кто-то пытается получить доступ к безопасному маршруту, но не вошел в систему, перенаправьте его на страницу входа.

Все остальное будет исходить из команды create-react-app вы запускали ранее.

Добавить страницы в приложение ReactJS

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

Добавьте папку components папку client/src . Это где все ваши компоненты будут жить и самый простой способ их организовать. Затем создайте home папку для компонентов домашней страницы. На данный момент будет только один, но позже может быть больше компонентов, используемых только для домашней страницы. Добавьте файл HomePage.js в папку со следующим содержимым:

 import React from 'react'; export default class HomePage extends React.Component{ render(){ return( <h1>Home Page</h1> ); } } 

Это все, что вам действительно нужно для домашней страницы на данный момент. Самый важный момент — сделать компонент HomePage типом класса. Хотя сейчас он содержит только один тег h1 , он должен быть «страницей», то есть, вероятно, он будет содержать другие компоненты, поэтому важно, чтобы он был контейнерным компонентом.

Затем создайте папку auth в components . Здесь будут жить все компоненты, связанные с аутентификацией. В этой папке создайте файл LoginForm.js .

Первое, на что нужно обратить внимание, это то, что вы будете использовать withAuth высшего порядка withAuth из React SDK от Okta, чтобы обернуть всю форму входа в систему. Это добавляет реквизит к компоненту с именем auth , позволяя получить доступ к таким вещам, как функции isAuthenticated и redirect в этом компоненте высшего порядка.

Код для компонента LoginForm выглядит следующим образом:

 import React from 'react'; import OktaAuth from '@okta/okta-auth-js'; import { withAuth } from '@okta/okta-react'; export default withAuth(class LoginForm extends React.Component { constructor(props) { super(props); this.state = { sessionToken: null, error: null, username: '', password: '' } this.oktaAuth = new OktaAuth({ url: props.baseUrl }); this.handleSubmit = this.handleSubmit.bind(this); this.handleUsernameChange = this.handleUsernameChange.bind(this); this.handlePasswordChange = this.handlePasswordChange.bind(this); } handleSubmit(e) { e.preventDefault(); this.oktaAuth.signIn({ username: this.state.username, password: this.state.password }) .then(res => this.setState({ sessionToken: res.sessionToken })) .catch(err => { this.setState({error: err.message}); console.log(err.statusCode + ' error', err) }); } handleUsernameChange(e) { this.setState({ username: e.target.value }); } handlePasswordChange(e) { this.setState({ password: e.target.value }); } render() { if (this.state.sessionToken) { this.props.auth.redirect({ sessionToken: this.state.sessionToken }); return null; } const errorMessage = this.state.error ? <span className="error-message">{this.state.error}</span> : null; return ( <form onSubmit={this.handleSubmit}> {errorMessage} <div className="form-element"> <label>Username:</label> <input id="username" type="text" value={this.state.username} onChange={this.handleUsernameChange} /> </div> <div className="form-element"> <label>Password:</label> <input id="password" type="password" value={this.state.password} onChange={this.handlePasswordChange} /> </div> <input id="submit" type="submit" value="Submit" /> </form> ); } }); 

OktaAuth вещь, которую стоит отметить, это OktaAuth библиотека OktaAuth . Это базовая библиотека для таких вещей, как вход в приложение Okta, которое вы создали ранее. Вы заметите, что в конструкторе создается объект baseUrl передается свойство baseUrl . Это URL-адрес издателя, который находится в вашем файле app.config.js . Компонент LoginForm должен содержаться в другом компоненте, поэтому вам нужно будет создать файл LoginPage.js содержащий этот компонент. Вы снова будете использовать withAuth высшего порядка withAuth , чтобы получить доступ к функции isAuthenticated . Содержимое LoginPage.js будет:

 import React, { Component } from 'react'; import { Redirect } from 'react-router-dom'; import LoginForm from './LoginForm'; import { withAuth } from '@okta/okta-react'; export default withAuth(class Login extends Component { constructor(props) { super(props); this.state = { authenticated: null }; this.checkAuthentication = this.checkAuthentication.bind(this); this.checkAuthentication(); } async checkAuthentication() { const authenticated = await this.props.auth.isAuthenticated(); if (authenticated !== this.state.authenticated) { this.setState({ authenticated }); } } componentDidUpdate() { this.checkAuthentication(); } render() { if (this.state.authenticated === null) return null; return this.state.authenticated ? <Redirect to={{ pathname: '/profile' }} /> : <LoginForm baseUrl={this.props.baseUrl} />; } }); 

Хотя это немного меньше, чем в компоненте формы входа в систему, здесь есть несколько важных моментов, на которые следует обратить внимание.

Опять же, вы используете withAuth высшего порядка withAuth . Это будет повторяющаяся тема для каждого компонента, который должен использовать процесс аутентификации или авторизации Okta. В этом случае он в основном используется для получения функции isAuthenticated . Метод checkAuthentication() выполняется в конструкторе и в методе жизненного цикла componentDidUpdate чтобы гарантировать, что при создании компонента он проверяется, и каждое последующее изменение компонента проверяется снова.

Когда isAuthenticated возвращает true, он устанавливается в состояние компонента. Затем он проверяется в методе рендеринга, чтобы решить, показывать LoginForm компонент LoginForm или перенаправлять на страницу профиля пользователя, компонент, который вы создадите следующим.

Теперь создайте компонент ProfilePage.js в папке auth . Содержимое компонента:

 import React from 'react'; import { withAuth } from '@okta/okta-react'; export default withAuth(class ProfilePage extends React.Component { constructor(props){ super(props); this.state = { user: null }; this.getCurrentUser = this.getCurrentUser.bind(this); } async getCurrentUser(){ this.props.auth.getUser() .then(user => this.setState({user})); } componentDidMount(){ this.getCurrentUser(); } render() { if(!this.state.user) return null; return ( <section className="user-profile"> <h1>User Profile</h1> <div> <label>Name:</label> <span>{this.state.user.name}</span> </div> </section> ) } }); 

withAuth компонент withAuth предоставляет вам доступ к функции getUser . Здесь он вызывается из componentDidMount который является обычным местом для извлечения данных, которые будут использоваться в методе render . Единственная странная вещь, которую вы можете увидеть — это первая строка метода render которая ничего не render , пока на самом деле пользователь не вернется из асинхронного вызова getUser . Когда пользователь находится в состоянии, он отображает содержимое профиля, которое в данном случае просто отображает имя пользователя, вошедшего в систему.

Далее вы добавите компонент регистрации. Это можно сделать так же, как форму входа в систему, где есть компонент LoginForm который содержится в компоненте LoginPage . Чтобы продемонстрировать другой способ отображения этого, вы просто создадите компонент RegistrationForm который будет основным компонентом контейнера. Создайте файл RegistrationForm.js в папке auth со следующим содержимым:

 import React from 'react'; import OktaAuth from '@okta/okta-auth-js'; import { withAuth } from '@okta/okta-react'; import config from '../../app.config'; export default withAuth(class RegistrationForm extends React.Component{ constructor(props){ super(props); this.state = { firstName: '', lastName: '', email: '', password: '', sessionToken: null }; this.oktaAuth = new OktaAuth({ url: config.url }); this.checkAuthentication = this.checkAuthentication.bind(this); this.checkAuthentication(); this.handleSubmit = this.handleSubmit.bind(this); this.handleFirstNameChange = this.handleFirstNameChange.bind(this); this.handleLastNameChange = this.handleLastNameChange.bind(this); this.handleEmailChange = this.handleEmailChange.bind(this); this.handlePasswordChange = this.handlePasswordChange.bind(this); } async checkAuthentication() { const sessionToken = await this.props.auth.getIdToken(); if (sessionToken) { this.setState({ sessionToken }); } } componentDidUpdate() { this.checkAuthentication(); } handleFirstNameChange(e){ this.setState({firstName:e.target.value}); } handleLastNameChange(e) { this.setState({ lastName: e.target.value }); } handleEmailChange(e) { this.setState({ email: e.target.value }); } handlePasswordChange(e) { this.setState({ password: e.target.value }); } handleSubmit(e){ e.preventDefault(); fetch('/api/users', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify(this.state) }).then(user => { this.oktaAuth.signIn({ username: this.state.email, password: this.state.password }) .then(res => this.setState({ sessionToken: res.sessionToken })); }) .catch(err => console.log); } render(){ if (this.state.sessionToken) { this.props.auth.redirect({ sessionToken: this.state.sessionToken }); return null; } return( <form onSubmit={this.handleSubmit}> <div className="form-element"> <label>Email:</label> <input type="email" id="email" value={this.state.email} onChange={this.handleEmailChange}/> </div> <div className="form-element"> <label>First Name:</label> <input type="text" id="firstName" value={this.state.firstName} onChange={this.handleFirstNameChange} /> </div> <div className="form-element"> <label>Last Name:</label> <input type="text" id="lastName" value={this.state.lastName} onChange={this.handleLastNameChange} /> </div> <div className="form-element"> <label>Password:</label> <input type="password" id="password" value={this.state.password} onChange={this.handlePasswordChange} /> </div> <input type="submit" id="submit" value="Register"/> </form> ); } }); 

Этот компонент очень похож на компонент LoginForm за исключением того, что он вызывает Node API (который вы LoginForm через мгновение), который будет обрабатывать регистрацию. После завершения регистрации с помощью Node API компонент регистрирует вновь созданного пользователя, а метод рендеринга (когда он видит токен сеанса в состоянии) перенаправляет пользователя на домашнюю страницу приложения.

Вы также можете заметить свойство sessionToken в состоянии компонента. Это устанавливается handleSubmit() с целью обработки входа в систему, если регистрация прошла успешно. Затем он также используется методом render() для перенаправления после завершения входа в систему и получения токена.

Добавить маршруты в приложение React

Сначала добавьте компонент навигации для маршрутов, которые вы будете добавлять. В папке client/src/components добавьте папку с именем shared . Это будет место, где будут расположены все компоненты, которые используются в нескольких местах приложения. В этой новой папке добавьте файл с именем Navigation.js . Файл содержит базовый компонент со ссылками на все страницы приложения.

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

 import React from 'react'; import { Link } from 'react-router-dom'; import { withAuth } from '@okta/okta-react'; export default withAuth(class Navigation extends React.Component { constructor(props) { super(props); this.state = { authenticated: null }; this.checkAuthentication = this.checkAuthentication.bind(this); this.checkAuthentication(); } async checkAuthentication() { const authenticated = await this.props.auth.isAuthenticated(); if (authenticated !== this.state.authenticated) { this.setState({ authenticated }); } } componentDidUpdate() { this.checkAuthentication(); } render() { if (this.state.authenticated === null) return null; const authNav = this.state.authenticated ? <ul className="auth-nav"> <li><a href="javascript:void(0)" onClick={this.props.auth.logout}>Logout</a></li> <li><Link to="/profile">Profile</Link></li> </ul> : <ul className="auth-nav"> <li><a href="javascript:void(0)" onClick={this.props.auth.login}>Login</a></li> <li><Link to="/register">Register</Link></li> </ul>; return ( <nav> <ul> <li><Link to="/">Home</Link></li> {authNav} </ul> </nav> ) } }); 

Теперь, когда у вас есть компоненты, доступные для обработки всех маршрутов, создайте маршруты для них. Обновите файл App.js чтобы окончательная версия выглядела следующим образом:

 import React, { Component } from 'react'; import { Route } from 'react-router-dom'; import { SecureRoute, ImplicitCallback } from '@okta/okta-react'; import Navigation from './components/shared/Navigation'; import HomePage from './components/home/HomePage'; import RegistrationForm from './components/auth/RegistrationForm'; import config from './app.config'; import LoginPage from './components/auth/LoginPage'; import ProfilePage from './components/auth/ProfilePage'; import './App.css'; export default class App extends Component { render() { return ( <div className="App"> <Navigation /> <main> <Route path="/" exact component={HomePage} /> <Route path="/login" render={() => <LoginPage baseUrl={config.url} />} /> <Route path="/implicit/callback" component={ImplicitCallback} /> <Route path="/register" component={RegistrationForm} /> <SecureRoute path="/profile" component={ProfilePage} /> </main> </div> ); } } 

Здесь есть пара замечаний. Импорт компонентов SecureRoute и ImplicitCallback из Reta SDK от Okta. Компонент ImplicitCallback обрабатывает обратный вызов из потока аутентификации, чтобы обеспечить наличие конечной точки в приложении React для перехвата обратного вызова из Okta. Компонент SecureRoute позволяет защитить любой маршрут и перенаправить неаутентифицированных пользователей на страницу входа.

Компонент Route от React Router делает именно то, что вы ожидаете: он принимает путь, по которому прошел пользователь, и устанавливает компонент для обработки этого маршрута. Компонент SecureRoute выполняет дополнительную проверку, чтобы убедиться, что пользователь вошел в систему, прежде чем разрешить доступ к этому маршруту. Если это не так, будет onAuthRequired функция onAuthRequired в index.js , чтобы заставить пользователя перейти на страницу входа.

Единственная другая действительно странная вещь здесь — это путь для входа в систему. Вместо того, чтобы просто устанавливать компонент для обработки пути, он запускает метод render который визуализирует компонент LoginPage и устанавливает baseUrl из конфигурации.

Добавить конечные точки API в приложение Node

Возможно, вы помните, что Node API выполняет регистрацию, поэтому вам нужно добавить конечную точку в приложение Node для обработки этого вызова. Для этого вам нужно добавить Node SDK от Okta. Из папки `api` запустите:

 npm install @okta/okta-sdk-nodejs --save 

Затем вы измените файл users.js в api/routes users.js . Файл будет выглядеть так:

 const express = require('express'); const router = express.Router(); const oktaClient = require('../lib/oktaClient'); /* Create a new User (register). */ router.post('/', (req, res, next) => { if (!req.body) return res.sendStatus(400); const newUser = { profile: { firstName: req.body.firstName, lastName: req.body.lastName, email: req.body.email, login: req.body.email }, credentials: { password: { value: req.body.password } } }; oktaClient.createUser(newUser) .then(user => { res.status(201); res.send(user); }) .catch(err => { res.status(400); res.send(err); }) }); module.exports = router; 

Здесь следует обратить внимание на импорт lib/oktaClient (который вы добавите lib/oktaClient ), вызов функции oktaClient и форму объекта newUser . Форма объекта newUser документирована в документации API Okta .

Чтобы приложение Node могло выполнять вызовы в приложении Okta, ему потребуется токен API. Чтобы создать его, перейдите на панель инструментов разработчика Okta, наведите курсор мыши на пункт меню API и нажмите «Жетоны».

экран жетонов окта апи

Оттуда нажмите Создать токен. Дайте токену имя типа «Членство» и нажмите «Создать токен».

создать экран токена

Скопируйте токен в безопасное место для последующего использования.

Создайте файл с именем oktaClient.js в новой папке с именем lib в приложении Node. Файл настроит объект Client из Okta SDK Node с использованием только что созданного API-токена:

 const okta = require('@okta/okta-sdk-nodejs'); const client = new okta.Client({ orgUrl: '{yourOktaDomain}', token: '{yourApiToken}' }); module.exports = client; 

В файле app.js в корне приложения Node обновите файл, чтобы все вызовы перенаправлялись в /api/<something> . Вы увидите раздел под блоком операторов app.use . Измените маршрут, настроенный так, чтобы он выглядел так:

 app.use('/api', index); app.use('/api/users', users); 

Если ваше приложение Node все еще работает, вам нужно будет остановить приложение (с помощью CTRL + C) и перезапустить его (с npm start ), чтобы обновления вступили в силу.

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

страница профиля пользователя

Учить больше

Если вы хотите узнать больше о технологиях, используемых в этих статьях, вы можете ознакомиться с документацией для:

Также ознакомьтесь с другими статьями, использующими Okta для аутентификации:

Как всегда, если у вас есть вопросы, комментарии или замечания по поводу статьи, вы можете написать мне по адресу [email protected] или опубликовать свои вопросы на форумах разработчиков . Чтобы узнать больше статей и руководств, следите за нами в Twitter @OktaDev .