Authentication

Authentication

2020/11/10 (更新內容)

設定

進入firebase console,先選擇登入方式,並啟用登入方式。我們先使用電子郵件/密碼,讓firebase幫我們管理帳號。

firebase authentication.ppt
  • 進入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 Navigation