Authentication
Authentication
2020/11/10 (更新內容)
設定
進入firebase console,先選擇登入方式,並啟用登入方式。我們先使用電子郵件/密碼,讓firebase幫我們管理帳號。
![](https://www.google.com/images/icons/product/drive-32.png)
進入console
選擇登入方式
電子郵件
更動Firestore規則
介面
我們先設計一個註冊的介面
/src/account/SignUp.js
import React, {useState} from 'react';
import {Button, View, Text, TextInput } from 'react-native';
import styles from '../styles';
export default function SignUp() {
const [displayName, setDisplayName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
return(
<View style={styles.form}>
<TextInput
style={styles.inputStyle}
placeholder="姓名"
value={displayName}
onChangeText={text=>setDisplayName(text)}
/>
<TextInput
style={styles.inputStyle}
placeholder="電子信箱"
value={email}
onChangeText={text=>setEmail(text)}
/>
<TextInput
style={styles.inputStyle}
placeholder="密碼"
value={password}
onChangeText={text=>setPassword(text)}
maxLength={15}
secureTextEntry={true}
/>
<Button
title="註冊"
/>
<Text>已經註冊,我要登入</Text>
</View>
)
}
加上相關的style (form、inputStyle)。
/src/styles.js
import {StatusBar, StyleSheet} from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
marginTop: StatusBar.currentHeight || 0,
},
loading:{
flex:1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
item: {
flex: 1,
flexDirection: 'row',
backgroundColor: '#00ffff',
padding: 8,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 24,
},
form: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
padding: 35,
marginTop: StatusBar.currentHeight || 0,
},
inputStyle: {
width: '100%',
marginBottom: 15,
paddingBottom: 15,
alignSelf: "center",
borderColor: "#ccc",
borderBottomWidth: 1
},
});
export default styles;
將SignUp加到App.js
改一下App.js
import React, {useState} from 'react';
//import { View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
//import { createStackNavigator } from '@react-navigation/stack';
import PersonList from './src/person/PersonList';
import ProductList from './src/product/ProductList';
import SignUp from './src/account/SignUp';
import Click from './Click';
//const Stack = createStackNavigator();
const Tab = createBottomTabNavigator();
function App() {
const [count, setCount] = useState(10);
let countString = "count in App:"+count;
function updateCount(newCount){
setCount(newCount);
}
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="SignUp" component={SignUp} />
<Tab.Screen name="Person" component={PersonList} />
<Tab.Screen name="Product" component={ProductList} />
<Tab.Screen name="Click" component={Click} initialParams={{ count: 10 }}/>
</Tab.Navigator>
</NavigationContainer>
);
}
export default App;
這樣就可以看到註冊的介面了,再加上註冊的邏輯。
註冊
利用firebase提供的createUserWithEmailAndPassword(),將使用者輸入的帳號與密碼傳給firebase進行註冊。
const res = await firebase.auth()
.createUserWithEmailAndPassword(email, password);
利用user.updateProfile去設定在firebase裡的名稱。
res.user.updateProfile({displayName: displayName});
/src/account/SignUp.js
import React, {useState} from 'react';
import {Button, View, Text, TextInput } from 'react-native';
import * as firebase from 'firebase';
import * as FirebaseCore from 'expo-firebase-core';
import styles from '../styles';
export default function SignUp() {
const [displayName, setDisplayName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [message, setMessage] = useState("");//for error message from signUp
if (!firebase.apps.length) {
firebase.initializeApp(FirebaseCore.DEFAULT_WEB_APP_OPTIONS);
}
async function signUp(){
try {
const res = await firebase.auth()
.createUserWithEmailAndPassword(email, password);
res.user.updateProfile({displayName: displayName});
//console.log('User registered successfully!');
setDisplayName('');
setEmail('');
setPassword('');
setMessage('');
}
catch(error){
setMessage(error.message);
}
}
return(
<View style={styles.form}>
<TextInput
style={styles.inputStyle}
placeholder="姓名"
value={displayName}
onChangeText={text=>setDisplayName(text)}
/>
<TextInput
style={styles.inputStyle}
placeholder="電子信箱"
value={email}
onChangeText={text=>setEmail(text)}
/>
<TextInput
style={styles.inputStyle}
placeholder="密碼"
value={password}
onChangeText={text=>setPassword(text)}
maxLength={15}
secureTextEntry={true}
/>
<Button
title="註冊"
onPress={signUp}
/>
<Text>{message}</Text>
<Text>
已經註冊,我要登入
</Text>
</View>
)
}
登入
登入跟註冊很像,利用firebase提供的signInWithEmailAndPassword(),將使用者輸入的帳號與密碼傳給firebase進行驗證。
const res= firebase.auth()
.signInWithEmailAndPassword(email, password);
src/account/SignIn.js
import React, {useState} from 'react';
import {Button, View, Text, TextInput } from 'react-native';
import * as firebase from 'firebase';
import * as FirebaseCore from 'expo-firebase-core';
import styles from '../styles';
export default function SignIn() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [message, setMessage] = useState("");
if (!firebase.apps.length) {
firebase.initializeApp(FirebaseCore.DEFAULT_WEB_APP_OPTIONS);
}
async function signIn(){
try {
const res= firebase.auth()
.signInWithEmailAndPassword(email, password);
console.log('User login successfully!');
setEmail('');
setPassword('');
setMessage('');
}
catch(error){
setMessage(error.message);
}
};
return(
<View style={styles.form}>
<TextInput
style={styles.inputStyle}
placeholder="電子信箱"
value={email}
onChangeText={text=>setEmail(text)}
/>
<TextInput
style={styles.inputStyle}
placeholder="密碼"
value={password}
onChangeText={text=>setPassword(text)}
maxLength={15}
secureTextEntry={true}
/>
<Button
title="登入"
onPress={signIn}
/>
<Text>{message}</Text>
<Text>
尚未註冊,我要註冊
</Text>
</View>
)
}
改一下App.js
import React, {useState} from 'react';
//import { View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
//import { createStackNavigator } from '@react-navigation/stack';
import PersonList from './src/person/PersonList';
import ProductList from './src/product/ProductList';
import SignIn from './src/account/SignIn';
import Click from './Click';
//const Stack = createStackNavigator();
const Tab = createBottomTabNavigator();
function App() {
const [count, setCount] = useState(10);
let countString = "count in App:"+count;
function updateCount(newCount){
setCount(newCount);
}
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="SignIn" component={SignIn} />
<Tab.Screen name="Person" component={PersonList} />
<Tab.Screen name="Product" component={ProductList} />
<Tab.Screen name="Click" component={Click} initialParams={{ count: 10 }}/>
</Tab.Navigator>
</NavigationContainer>
);
}
export default App;
將firestore的存取規則改為:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if true;
allow write: if request.auth != null;
}
}
}
當我們登入時,我們可以看到內容,也可以新增內容。但是,如果沒有登入,新增內容時就會被firebase拒絕。當然,看系統的需求,也可以設定為登入才可看到內容。
登出
登出跟登入、註冊很像,利用firebase提供的signOut()。
firebase.auth().signOut();
接下來是登出:
import React, {useState} from 'react';
import {Button, View, Text } from 'react-native';
import * as firebase from 'firebase';
import * as FirebaseCore from 'expo-firebase-core';
import styles from '../styles';
export default function SignOut() {
if (!firebase.apps.length) {
firebase.initializeApp(FirebaseCore.DEFAULT_WEB_APP_OPTIONS);
}
const [message, setMessage] = useState("");
async function signOut(){
try{
await firebase.auth().signOut();
console.log('User signed out successfully!');
}
catch(error){
setMessage(error.message);
}
};
return(
<View style={styles.form}>
<Text>{message}</Text>
<Button
title="登出"
onPress={signOut}
/>
<Text>{message}</Text>
<Text>
我要登入
</Text>
</View>
)
}
修改一下App.js
import React, {useState} from 'react';
import { View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
//import { createStackNavigator } from '@react-navigation/stack';
import PersonList from './src/person/PersonList';
import ProductList from './src/product/ProductList';
import SignIn from './src/account/SignIn';
import SignOut from './src/account/SignOut';
//const Stack = createStackNavigator();
const Tab = createBottomTabNavigator();
function App() {
const [count, setCount] = useState(10);
let countString = "count in App:"+count;
function updateCount(newCount){
setCount(newCount);
}
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="SignIn" component={SignIn} />
<Tab.Screen name="SignOut" component={SignOut} />
<Tab.Screen name="Person" component={PersonList} />
<Tab.Screen name="Product" component={ProductList} />
<Tab.Screen name="Click" component={Click} initialParams={{ count: 10 }}/>
</Tab.Navigator>
</NavigationContainer>
);
}
export default App;
這樣子,我們就可以登入、登出了。登出之後,就無法新增資料了。
自動登入
一般而言,手機都會在成功登入後,儲存帳號、密碼,下一次就可以自動登入。這時候可以利用 persistent state 裡介紹的SecureStore。不過,我們在這裡利用async/await語法。
/src/account/SignIn.js
import React, {useState, useEffect} from 'react';
import {Button, View, Text, TextInput } from 'react-native';
import * as firebase from 'firebase';
import * as FirebaseCore from 'expo-firebase-core';
import * as SecureStore from 'expo-secure-store';
import styles from '../styles';
export default function SignIn() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [message, setMessage] = useState("");
if (!firebase.apps.length) {
firebase.initializeApp(FirebaseCore.DEFAULT_WEB_APP_OPTIONS);
}
async function signIn(){
try {
const res= await firebase.auth()
.signInWithEmailAndPassword(email, password);
console.log('User login successfully!');
const loginString = JSON.stringify({email:email, password:password});
await SecureStore.setItemAsync("login", loginString);
setEmail('');
setPassword('');
setMessage('');
//console.log("account saved");
}
catch(error){
setMessage(error.message);
}
};
async function getAccount(){
try {
console.log("getAccount");
setMessage("getting username");
const loginString = await SecureStore.getItemAsync("login");
const login = JSON.parse(loginString);
setEmail(login.email);
setPassword(login.password);
setMessage("");
}
catch(error){
setMessage(error.message)
}
}
useEffect(()=>{getAccount()},[]);
return(
<View style={styles.form}>
<TextInput
style={styles.inputStyle}
placeholder="電子信箱"
value={email}
onChangeText={text=>setEmail(text)}
/>
<TextInput
style={styles.inputStyle}
placeholder="密碼"
value={password}
onChangeText={text=>setPassword(text)}
maxLength={15}
secureTextEntry={true}
/>
<Button
title="登入"
onPress={signIn}
/>
<Text>{message}</Text>
<Text>
尚未註冊,我要註冊
</Text>
</View>
)
}
作業
一般而言,不會把登入、登出都放在tab navigation上。要如何處理登入、登出頁面呢?
根據以上的範例,在登入畫面,讓使用者選擇是否記住密碼,如果選擇記住密碼,則將密碼儲存在SecureStore。
讓使用者可以登出並清除SecureStore裡的帳號密碼,讓使用者可以使用其他帳號登入,或不再記住密碼。
當帳號密碼被清除時,getItemAsync不會進入catch,只是回傳值是null而已。
參考資料
Firebase
React Native Firebase – Login and User Registration Tutorial
Prerequisite
Install React Native Project
Firebase Account Setup
Set up Firebase in React Native
Initiate Navigation in React Native
Register with Email and Password
Login User with email and password
Logout from Firebase via React Native
How to build an Email Authentication app with Firebase, Firestore and React Native
Requirements
Create a Firebase Project
Generate an Expo App
Integrate Firebase SDK
Environment Variables
Creating UI Screens
Adding Navigation
Login & Signup using Firebase
Adding Redux
Update Signup and Login Screens
Creating a Firestore Database
On Signup, save the user to Firestore
On Login, fetch the user from Firestore
React Navigation
Authentication flows (react navigation 5)