Статьи

Аутентификация в React Native с Firebase

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

React Native — один из самых популярных вариантов создания кроссплатформенных мобильных приложений с использованием JavaScript и React.

Для многих приложений необходима регистрация и аутентификация пользователей, и в этом руководстве я собираюсь использовать Firebase для реализации аутентификации в приложении React Native.

Я тестирую на Android, но код в этом уроке должен работать и на iOS. Я предполагаю, что вы уже работали с React Native, поэтому я не буду вдаваться в подробности всего кода React Native. Если вы новичок в React Native, я рекомендую вам прочитать мой предыдущий учебник о том, как создать приложение для Android с React Native .

Вот как будет выглядеть финальное приложение:

реагировать на собственное приложение для аутентификации Firebase

Окончательный код на GitHub .

Создание приложения Firebase

Для работы с Firebase сначала необходимо создать приложение на Firebase. Войдите в свою панель и создайте новое приложение. Вам нужно будет изменить имя на что-то уникальное.

создать новое приложение Firebase

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

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

включить аутентификацию по электронной почте и паролю

Сборка приложения

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

Настроить

Создайте проект, выполнив следующую команду:

react-native init rnfirebaseauth 

Далее установите React родной одаренной прядильщика и Firebase :

 npm install react-native-gifted-spinner firebase --save 

Как следует из названия, «React native одаренный прядильщик» позволяет создавать прядильщики для указания того, что приложение что-то загружает. Это приложение будет использовать спиннер во время общения с Firebase.

Структура каталогов

Создайте папку src внутри директории вашего проекта и внутри создайте папку компонентов , страниц и стилей .

Ваша структура каталогов теперь должна выглядеть так:

 rnfirebaseauth android ios node_modules package.json index.android.js index.ios.js src components pages styles 

Вот для чего будет каждая папка в каталоге src :

  • Компоненты : содержит пользовательские компоненты, используемые приложением. Главным образом для удобства, чтобы вам не приходилось писать много кода при использовании различных компонентов пользовательского интерфейса, таких как кнопки и заголовки.
  • страницы : содержит отдельные страницы приложения.
  • Стили : содержит общие стили, используемые в приложении.

Компоненты

кнопка

Компонент кнопки позволяет создавать кнопки. Он использует props для указания текста кнопки, стилей и функции, выполняемой при нажатии кнопки. Создайте компоненты / button.js и добавьте следующий код:

 'use strict'; import React, { AppRegistry, Component, Text, View, TouchableHighlight } from 'react-native'; export default class button extends Component { render(){ return ( <View> <TouchableHighlight underlayColor={"#E8E8E8"} onPress={this.props.onpress} style={this.props.button_styles}> <View> <Text style={this.props.button_text_styles}>{this.props.text}</Text> </View> </TouchableHighlight> </View> ); } } AppRegistry.registerComponent('button', () => button); 

Компонент заголовка позволяет создавать заголовки. Заголовок имеет заголовок и спиннер, который показывает, когда loaded props имеет значение false . В блесне используется установленный ранее одаренный блесна React. Создайте компоненты / header.js и добавьте следующий код:

 'use strict'; import React, { AppRegistry, Component, StyleSheet, Text, TextInput, View } from 'react-native'; import GiftedSpinner from 'react-native-gifted-spinner'; export default class header extends Component { render(){ return ( <View style={styles.header}> <View style={styles.header_item}> <Text style={styles.header_text}>{this.props.text}</Text> </View> <View style={styles.header_item}> { !this.props.loaded && <GiftedSpinner /> } </View> </View> ); } } const styles = StyleSheet.create({ header: { padding: 10, flexDirection: 'row', alignItems: 'center', marginBottom: 20, flex: 1 }, header_item: { paddingLeft: 10, paddingRight: 10 }, header_text: { color: '#000', fontSize: 18 } }); AppRegistry.registerComponent('header', () => header); 

страницы

Страница регистрации

Страница регистрации является страницей приложения по умолчанию и позволяет пользователю создать учетную запись. Создайте pages / signup.js и добавьте следующее:

 'use strict'; import React, { AppRegistry, Component, Text, TextInput, View } from 'react-native'; import Button from '../components/button'; import Header from '../components/header'; import Login from './login'; import Firebase from 'firebase'; let app = new Firebase("YOUR-FIREBASE-APP-URL"); import styles from '../styles/common-styles.js'; export default class signup extends Component { constructor(props){ super(props); this.state = { loaded: true, email: '', password: '' }; } signup(){ this.setState({ loaded: false }); app.createUser({ 'email': this.state.email, 'password': this.state.password }, (error, userData) => { if(error){ switch(error.code){ case "EMAIL_TAKEN": alert("The new user account cannot be created because the email is already in use."); break; case "INVALID_EMAIL": alert("The specified email is not a valid email."); break; default: alert("Error creating user:"); } }else{ alert('Your account was created!'); } this.setState({ email: '', password: '', loaded: true }); }); } goToLogin(){ this.props.navigator.push({ component: Login }); } render() { return ( <View style={styles.container}> <Header text="Signup" loaded={this.state.loaded} /> <View style={styles.body}> <TextInput style={styles.textinput} onChangeText={(text) => this.setState({email: text})} value={this.state.email} placeholder={"Email Address"} /> <TextInput style={styles.textinput} onChangeText={(text) => this.setState({password: text})} value={this.state.password} secureTextEntry={true} placeholder={"Password"} /> <Button text="Signup" onpress={this.signup.bind(this)} button_styles={styles.primary_button} button_text_styles={styles.primary_button_text} /> <Button text="Got an Account?" onpress={this.goToLogin.bind(this)} button_styles={styles.transparent_button} button_text_styles={styles.transparent_button_text} /> </View> </View> ); } } AppRegistry.registerComponent('signup', () => signup); 

Разбивка кода выше. Сначала импортируйте реагирующую версию и извлекайте все необходимое из класса React .

 import React, { AppRegistry, Component, Text, TextInput, View } from 'react-native'; 

Импортируйте компоненты кнопки и заголовка:

 import Button from '../components/button'; import Header from '../components/header'; 

Импортируйте страницу входа:

 import Login from './login'; 

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

Примечание . Вместо указания полного URL-адреса, такого как http://your-app-name.firebasio.com, следует указать your-app-name.firebaseio.com . Вам также необходимо заменить YOUR-FIREBASE-APP-URL в каждом файле.

 import Firebase from 'firebase'; let app = new Firebase("YOUR-FIREBASE-APP-URL"); 

Импортируйте общие стили:

 import styles from '../styles/common-styles.js'; 

Создайте новый компонент и экспортируйте его для импорта в другие файлы.

 export default class signup extends Component { ... } 

В конструкторе установите состояние по умолчанию. loaded наборы, показывать ли счетчик. Если loaded true тогда счетчик скрыт, иначе счетчик виден. email и password являются значениями по умолчанию для текстовых полей электронной почты и пароля.

 constructor(props){ super(props); this.state = { loaded: true, email: '', password: '' }; } 

Метод signup выполняется, когда пользователь нажимает кнопку регистрации. Первая настройка loaded в false чтобы показать счетчик. Затем вызовите метод createUser в приложении createUser . Этот метод принимает объект, содержащий электронную почту и пароль пользователя в качестве первого аргумента, и функцию обратного вызова в качестве второго. Если error не пуста, предупредите пользователя на основе свойства code error . В противном случае предположим, что учетная запись была создана. Наконец установите email и password в пустую строку, чтобы сбросить значение текстовых полей.

 signup(){ this.setState({ loaded: false }); app.createUser({ 'email': this.state.email, 'password': this.state.password }, (error, userData) => { if(error){ switch(error.code){ case "EMAIL_TAKEN": alert("The new user account cannot be created because the email is already in use."); break; case "INVALID_EMAIL": alert("The specified email is not a valid email."); break; default: alert("Error creating user:"); } }else{ alert('Your account was created!'); } this.setState({ email: '', password: '', loaded: true }); }); } 

Функция goToLogin перейти на страницу входа. Это работает с помощью метода push компонента Navigator. Метод push принимает объект, содержащий компонент, который вы хотите отобразить.

 goToLogin(){ this.props.navigator.push({ component: Login }); } 

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

 render() { return ( <View style={styles.container}> <Header text="Signup" loaded={this.state.loaded} /> <View style={styles.body}> <TextInput style={styles.textinput} onChangeText={(text) => this.setState({email: text})} value={this.state.email} placeholder={"Email Address"} /> <TextInput style={styles.textinput} onChangeText={(text) => this.setState({password: text})} value={this.state.password} secureTextEntry={true} placeholder={"Password"} /> <Button text="Signup" onpress={this.signup.bind(this)} button_styles={styles.primary_button} button_text_styles={styles.primary_button_text} /> <Button text="Got an Account?" onpress={this.goToLogin.bind(this)} button_styles={styles.transparent_button} button_text_styles={styles.transparent_button_text} /> </View> </View> ); } 

Обратите внимание на значение loaded в состоянии как значение для loaded атрибута в заголовке. Это позволяет контролировать показ счетчика из родительского компонента.

 <Header text="Signup" loaded={this.state.loaded} /> 

Для текстовых полей укажите атрибут onChangeText и передайте функцию стрелки, которая обновит значение этого конкретного поля в состоянии.

 onChangeText={(text) => this.setState({password: text})} 

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

 secureTextEntry={true} 

Для кнопок обратите внимание на использование bind для функции signup вместо непосредственного ее выполнения при нажатии кнопки. Это связано с тем, что методы в es6 не привязываются автоматически к текущему классу.

 <Button text="Signup" onpress={this.signup.bind(this)} button_styles={styles.primary_button} button_text_styles={styles.primary_button_text} /> 

Страница авторизации

Страница входа предназначена для входа в систему пользователей. Создайте pages / login.js и добавьте следующий код:

 'use strict'; import React, { AppRegistry, Component, StyleSheet, Text, TextInput, View, AsyncStorage } from 'react-native'; import Button from '../components/button'; import Header from '../components/header'; import Signup from './signup'; import Account from './account'; import Firebase from 'firebase'; let app = new Firebase("YOUR-FIREBASE-APP-URL"); import styles from '../styles/common-styles.js'; export default class login extends Component { constructor(props){ super(props); this.state = { email: '', password: '', loaded: true } } render(){ return ( <View style={styles.container}> <Header text="Login" loaded={this.state.loaded} /> <View style={styles.body}> <TextInput style={styles.textinput} onChangeText={(text) => this.setState({email: text})} value={this.state.email} placeholder={"Email Address"} /> <TextInput style={styles.textinput} onChangeText={(text) => this.setState({password: text})} value={this.state.password} secureTextEntry={true} placeholder={"Password"} /> <Button text="Login" onpress={this.login.bind(this)} button_styles={styles.primary_button} button_text_styles={styles.primary_button_text} /> <Button text="New here?" onpress={this.goToSignup.bind(this)} button_styles={styles.transparent_button} button_text_styles={styles.transparent_button_text} /> </View> </View> ); } login(){ this.setState({ loaded: false }); app.authWithPassword({ "email": this.state.email, "password": this.state.password }, (error, user_data) => { this.setState({ loaded: true }); if(error){ alert('Login Failed. Please try again'); }else{ AsyncStorage.setItem('user_data', JSON.stringify(user_data)); this.props.navigator.push({ component: Account }); } }); } goToSignup(){ this.props.navigator.push({ component: Signup }); } } AppRegistry.registerComponent('login', () => login); 

Здесь нет ничего нового, кроме функции login в login . Функция login вызывает метод authWithPassword из приложения Firebase, передавая объект, содержащий электронную почту и пароль пользователя, и функцию обратного вызова для выполнения после возврата ответа. Если в ответе нет ошибок, используйте AsyncStorage для сохранения пользовательских данных в локальном хранилище, вызвав метод setItem в объекте AsyncStorage . Этот метод принимает имя элемента и его значение.

Примечание . Вы можете хранить только строки, поэтому мы используем метод JSON.stringify для преобразования объекта user_data в строку. После этого перейдите на страницу учетной записи или предупредите пользователя о том, что вход не выполнен.

 login(){ this.setState({ loaded: false }); app.authWithPassword({ "email": this.state.email, "password": this.state.password }, (error, user_data) => { this.setState({ loaded: true }); if(error){ alert('Login Failed. Please try again'); }else{ AsyncStorage.setItem('user_data', JSON.stringify(user_data)); this.props.navigator.push({ component: Account }); } }); } 

Страница аккаунта

На странице учетной записи отображается основная информация о текущем пользователе. Создайте pages / account.js и добавьте следующее:

 'use strict'; import React, { AppRegistry, Component, StyleSheet, Text, View, Image, AsyncStorage } from 'react-native'; import Button from '../components/button'; import Header from '../components/header'; import Login from './login'; import styles from '../styles/common-styles.js'; import Firebase from 'firebase'; let app = new Firebase("YOUR-FIREBASE-APP-URL"); export default class account extends Component { constructor(props){ super(props); this.state = { loaded: false, } } componentWillMount(){ AsyncStorage.getItem('user_data').then((user_data_json) => { let user_data = JSON.parse(user_data_json); this.setState({ user: user_data, loaded: true }); }); } render(){ return ( <View style={styles.container}> <Header text="Account" loaded={this.state.loaded} /> <View style={styles.body}> { this.state.user && <View style={styles.body}> <View style={page_styles.email_container}> <Text style={page_styles.email_text}>{this.state.user.password.email}</Text> </View> <Image style={styles.image} source={{uri: this.state.user.password.profileImageURL}} /> <Button text="Logout" onpress={this.logout.bind(this)} button_styles={styles.primary_button} button_text_styles={styles.primary_button_text} /> </View> } </View> </View> ); } logout(){ AsyncStorage.removeItem('user_data').then(() => { app.unauth(); this.props.navigator.push({ component: Login }); }); } } const page_styles = StyleSheet.create({ email_container: { padding: 20 }, email_text: { fontSize: 18 } }); 

В отличие от других страниц, созданных до сих пор, эта страница имеет метод componentWillMount . Этот метод выполняется до монтирования компонента, поэтому он является идеальным местом для получения пользовательских данных из локального хранилища. На этот раз он использует метод getItem из объекта AsyncStorage , который принимает имя элемента в качестве аргумента. Чтобы получить сохраненное значение, используйте метод then и передайте функцию. Затем этой функции будет передано значение в качестве аргумента. Преобразуйте значение обратно в объект, используя JSON.parse затем установите его в текущее состояние. Таким образом, вы можете использовать this.state.user для извлечения любой информации из объекта пользователя.

 componentWillMount(){ AsyncStorage.getItem('user_data').then((user_data_json) => { let user_data = JSON.parse(user_data_json); this.setState({ user: user_data, loaded: true }); }); } 

Внутри метода render находится новый компонент Image . Это позволяет отображать изображение во многом как элемент img в HTML, но указав атрибут source с объектом, содержащим свойство uri . Это свойство uri ссылается на URL изображения, которое вы хотите отобразить.

 <Image style={styles.image} source={{uri: this.state.user.password.profileImageURL}} /> 

Стили

Каждый из компонентов включал src / styles / common-styles.js, но он еще не был создан. Файл служит глобальной таблицей стилей для всего приложения. Создайте файл и добавьте следующий код:

 'use strict'; import React, { StyleSheet } from 'react-native'; module.exports = StyleSheet.create({ container: { flex: 1, }, body: { flex: 9, alignItems: 'center', backgroundColor: '#F5FCFF', }, textinput: { height: 40, borderColor: 'red', borderWidth: 1 }, transparent_button: { marginTop: 10, padding: 15 }, transparent_button_text: { color: '#0485A9', fontSize: 16 }, primary_button: { margin: 10, padding: 15, backgroundColor: '#529ecc' }, primary_button_text: { color: '#FFF', fontSize: 18 }, image: { width: 100, height: 100 } }); 

Собираем все вместе

Теперь соберите все вместе, заменив код в index.android.js на приведенный ниже, или index.ios.js, если вы хотите выполнить развертывание на iOS.

 'use strict'; import React, { AppRegistry, Component, Text, View, Navigator, AsyncStorage } from 'react-native'; import Signup from './src/pages/signup'; import Account from './src/pages/account'; import Header from './src/components/header'; import Firebase from 'firebase'; let app = new Firebase("YOUR-FIREBASE-APP-URL"); import styles from './src/styles/common-styles.js'; class rnfirebaseauth extends Component { constructor(props){ super(props); this.state = { component: null, loaded: false }; } componentWillMount(){ AsyncStorage.getItem('user_data').then((user_data_json) => { let user_data = JSON.parse(user_data_json); let component = {component: Signup}; if(user_data != null){ app.authWithCustomToken(user_data.token, (error, authData) => { if(error){ this.setState(component); }else{ this.setState({component: Account}); } }); }else{ this.setState(component); } }); } render(){ if(this.state.component){ return ( <Navigator initialRoute={{component: this.state.component}} configureScene={() => { return Navigator.SceneConfigs.FloatFromRight; }} renderScene={(route, navigator) => { if(route.component){ return React.createElement(route.component, { navigator }); } }} /> ); }else{ return ( <View style={styles.container}> <Header text="React Native Firebase Auth" loaded={this.state.loaded} /> <View style={styles.body}></View> </View> ); } } } AppRegistry.registerComponent('rnfirebaseauth', () => rnfirebaseauth); 

Метод componentWillMount проверяет, хранятся ли user_data в локальном хранилище. Как и на предыдущей странице учетной записи, используйте AsyncStorage.getItem чтобы получить данные из локального хранилища, а затем проанализировать их. Если он возвращает null , то предположим, что в локальном хранилище ничего нет, и обновите состояние, чтобы установить страницу регистрации в качестве текущей страницы. В противном случае, попытайтесь аутентифицировать пользователя с токеном, который он пытался выполнить, используя последний адрес электронной почты и пароль, вызвав app.authWithCustomToken и передав ему токен. Если это удастся, установите текущую страницу на страницу учетной записи или на страницу регистрации.

 componentWillMount(){ AsyncStorage.getItem('user_data').then((user_data_json) => { let user_data = JSON.parse(user_data_json); let component = {component: Signup}; if(user_data != null){ app.authWithCustomToken(user_data.token, (error, authData) => { if(error){ this.setState(component); }else{ this.setState({component: Account}); } }); }else{ this.setState(component); } }); } 

Внутри метода render проверьте, установлен ли компонент в состоянии. Как вы видели ранее из метода constructor , это значение равно null поэтому оператор else будет выполнен по умолчанию. Внутри оператора else находится пользовательский интерфейс по умолчанию, который отображается при открытии приложения. После обновления состояния метод render вызывается снова, на этот раз выполняется код внутри условия if .

 if(this.state.component){ return ( <Navigator initialRoute={{component: this.state.component}} configureScene={() => { return Navigator.SceneConfigs.FloatFromRight; }} renderScene={(route, navigator) => { if(route.component){ return React.createElement(route.component, { navigator }); } }} /> ); }else{ return ( <View style={styles.container}> <Header text="React Native Firebase Auth" loaded={this.state.loaded} /> <View style={styles.body}></View> </View> ); } 

Внутри условия if компонент Navigator обрабатывает переходы между страницами. Это принимает initialRoute и renderScene и необязательный атрибут configureScene для настройки анимации при переходе между страницами. initialRoute позволяет вам указать объект, содержащий информацию о компоненте по умолчанию для визуализации с помощью навигатора. Метод renderScene принимает функцию, которая будет визуализировать компонент с route и navigator переданными в качестве аргумента этой функции. route — это объект, переданный в initialRoute .

С помощью route.component вы получаете фактическую ссылку на компонент и отображаете ее, используя React.createElement . Второй аргумент — это объект, содержащий props вы хотите передать визуализированному компоненту. В этом случае передается объект navigator , который содержит все методы, необходимые для навигации между различными страницами.

Если вы посмотрите на код для каждой из страниц (логин, регистрация, учетная запись), вы увидите, что объект navigator используется как this.props.navigator поскольку он был передан как props .

 <Navigator initialRoute={{component: this.state.component}} configureScene={() => { return Navigator.SceneConfigs.FloatFromRight; }} renderScene={(route, navigator) => { if(route.component){ return React.createElement(route.component, { navigator }); } }} /> 

Что дальше?

В этом руководстве вы создали приложение, которое аутентифицирует пользователей, использующих Firebase, с помощью комбинации адреса электронной почты и пароля. Firebase предлагает гораздо больше возможностей, когда дело доходит до аутентификации. Вы, возможно, заметили ранее, когда создавали приложение, которое Firebase позволяет вам использовать логины Facebook, Twitter, Github, Google, Anonymous и Custom. Если вы ищете другие способы аутентификации своих пользователей, я рекомендую вам проверить эти параметры.

Вы также узнали, как использовать AsyncStorage для локального сохранения пользовательских данных. Это позволяет приложению сохранять состояние входа в систему при последующих запусках приложения.

Firebase предоставляет вам функциональность, необходимую для мобильных приложений. Надеюсь, вы нашли этот урок полезным и приветствуем ваши комментарии и вопросы.