Authentication

Authentication

2021/11/16 (更新內容)

簡介

註冊

利用firebase提供的createUserWithEmailAndPassword(),將使用者輸入的帳號與密碼傳給firebase進行註冊 (詳參: Sign up new users )

import { getAuth, createUserWithEmailAndPassword } from "firebase/auth";


const auth = getAuth();

createUserWithEmailAndPassword(auth, email, password)

.then((userCredential) => {

// Signed in

const user = userCredential.user;

// ...

})

.catch((error) => {

const errorCode = error.code;

const errorMessage = error.message;

// ..

});

利用updateProfile去設定在firebase裡的名稱及頭像。(詳參: Update a user's profile )

import { getAuth, updateProfile } from "firebase/auth";

const auth = getAuth();

updateProfile(auth.currentUser, {

displayName: "Jane Q. User", photoURL: "https://example.com/jane-q-user/profile.jpg"

}).then(() => {

// Profile updated!

// ...

}).catch((error) => {

// An error occurred

// ...

});

登入

登入跟註冊很像,利用firebase提供的signInWithEmailAndPassword(),將使用者輸入的帳號與密碼傳給firebase進行驗證 (詳參: Sign in existing users )

import { getAuth, signInWithEmailAndPassword } from "firebase/auth";

const auth = getAuth();

signInWithEmailAndPassword(auth, email, password)

.then((userCredential) => {

// Signed in

const user = userCredential.user;

// ...

})

.catch((error) => {

const errorCode = error.code;

const errorMessage = error.message;

});

登出

登出跟登入、註冊很像,利用firebase提供的signOut()。

import { getAuth, signOut } from "firebase/auth";


const auth = getAuth();

signOut(auth).then(() => {

// Sign-out successful.

}).catch((error) => {

// An error happened.

});

firebase提供的範例使用的是.then()及.catch(),後面的範例採用aync await。

設定

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

firebase authentication.ppt
  • 進入console

  • 選擇登入方式

    • 電子郵件

  • 更動Firestore規則

介面

我們先設計一個註冊的介面

因為我們使用了password,Chrome會建議我們放在form裡,也建議開啟autoComplete,並設為current-password。

/src/account/SignUp.js

import React, {useState} from 'react';
import {Button, TextField} from '@mui/material';


export default function SignUp() {

const [account, setAccount] = useState({email:"",password:"", displayName:""});

const handleChange = function(e){

setAccount({...account,[e.target.name]:e.target.value})

}

return(

<form>

<TextField type = "text" name = "displayName" value={account.displayName}

placeholder="姓名" label="姓名:" onChange={handleChange} /><br/>

<TextField type = "email" name = "email" value={account.email}

placeholder="電子郵件信箱" label="電子郵件信箱:" onChange={handleChange}/><br/>

<TextField type = "password" name = "password" value={account.password}

placeholder="密碼" label="密碼:" onChange={handleChange} autoComplete="current-password"/><br/>

<Button variant="contained" color="primary">註冊</Button>
<Button variant="contained" color="secondary">已經註冊,我要登入</Button>

</form>

)


}

將SignUp加到Main.js

改一下Main.js

import React from 'react';


import AppMenu from './AppMenu';

import SignUp from '../account/SignUp';


export default function Main() {

return (

<div>

<AppMenu/>

<SignUp/>

</div>

)


}

這樣就可以看到註冊的介面了,再加上註冊的邏輯。

註冊

利用firebase提供的createUserWithEmailAndPassword(),將使用者輸入的帳號與密碼傳給firebase進行註冊。

const res = await createUserWithEmailAndPassword(auth, account.email, account.password);

利用updateProfile去設定在firebase裡的名稱。

updateProfile(auth.currentUser,{displayName: account.displayName});

另外,為了要顯示firebase回傳的錯誤訊息,加了message。

/src/account/SignUp.js

import React, {useState} from 'react';

import {Button, TextField} from '@mui/material';

import { getApps, initializeApp } from "firebase/app";

import { getAuth, createUserWithEmailAndPassword,updateProfile } from "firebase/auth";

import {config} from '../settings/firebaseConfig';


export default function SignUp() {

if (getApps().length===0) {

initializeApp(config);

}

const [account, setAccount] = useState({email:"",password:"", displayName:""});

const [message, setMessage] = useState("");

const handleChange = function(e){

setAccount({...account,[e.target.name]:e.target.value})

}

const handleSubmit = async function(){

try {

const auth = getAuth();

const res = await createUserWithEmailAndPassword(auth, account.email, account.password);

if (res) {

await updateProfile(auth.currentUser,{displayName: account.displayName});

}

setMessage("");


}

catch(error){

setMessage(""+error);

}

}

return(

<form>

<TextField type = "text" name = "displayName" value={account.displayName}

placeholder="姓名" label="姓名:" onChange={handleChange} /><br/>

<TextField type = "email" name = "email" value={account.email}

placeholder="電子郵件信箱" label="電子郵件信箱:" onChange={handleChange} autoComplete="email"/><br/>

<TextField type = "password" name = "password" value={account.password}

placeholder="密碼" label="密碼:" onChange={handleChange} autoComplete="current-password"/><br/>

{message}<br/>

<Button variant="contained" color="primary" onClick={handleSubmit}>註冊</Button>

<Button variant="contained" color="secondary">已經註冊,我要登入</Button>

</form>

)

}

登入

登入跟註冊很像,利用firebase提供的signInWithEmailAndPassword(),將使用者輸入的帳號與密碼傳給firebase進行驗證。

signInWithEmailAndPassword(auth, email, password)

先試著利用註冊改寫登入,再跟老師的範例比較一下

src/account/SignIn.js

import React, {useState} from 'react';

import {Button, TextField} from '@mui/material';

import { getApps, initializeApp } from "firebase/app";

import { getAuth, signInWithEmailAndPassword } from "firebase/auth";

import {config} from '../settings/firebaseConfig';


//import { Box } from '@mui/system';


export default function SignIn() {

if (getApps().length===0) {

initializeApp(config);

}

const [account, setAccount] = useState({email:"",password:"", displayName:""});

const [message, setMessage] = useState("");

const handleChange = function(e){

setAccount({...account,[e.target.name]:e.target.value})

}

const handleSubmit = async function(){

try {

const auth = getAuth();

const res = await signInWithEmailAndPassword(auth, account.email, account.password);

if (res) {

console.log(auth.currentUser.displayName);

}

setMessage("");


}

catch(error){

setMessage(""+error);

}

}

return(

<form>

<TextField type = "email" name = "email" value={account.email}

placeholder="電子郵件信箱" label="電子郵件信箱:" onChange={handleChange} autoComplete="email"/><br/>

<TextField type = "password" name = "password" value={account.password}

placeholder="密碼" label="密碼:" onChange={handleChange} autoComplete="current-password"/><br/>

{message}<br/>

<Button variant="contained" color="primary" onClick={handleSubmit}>登入</Button>

<Button variant="contained" color="secondary">我要註冊</Button>

</form>

)

}

試試看怎麼把註冊、登入串起來。可以使用router把頁面串起來,也可以使用state變數來控制。

改一下使用state變數來控制

src/ui/Main.js

import React, {useState} from 'react';


import AppMenu from './AppMenu';

import SignUp from '../account/SignUp';

import SignIn from '../account/SignIn';


export default function Main() {

const [status, setStatus] = useState("signIn");


return (

<div>

<AppMenu/>

{status==="signUp"?

<SignUp setStatus={setStatus}/>

:

<SignIn setStatus={setStatus}/>

}

</div>

)


}

修改一下

src/account/SignIn.js

import React, {useState} from 'react';

import {Button, TextField} from '@mui/material';

import { getApps, initializeApp } from "firebase/app";

import { getAuth, signInWithEmailAndPassword } from "firebase/auth";

import {config} from '../settings/firebaseConfig';


//import { Box } from '@mui/system';


export default function SignIn(props) {

if (getApps().length===0) {

initializeApp(config);

}

const [account, setAccount] = useState({email:"",password:"", displayName:""});

const [message, setMessage] = useState("");

const handleChange = function(e){

setAccount({...account,[e.target.name]:e.target.value})

}

const handleSubmit = async function(){

try {

const auth = getAuth();

const res = await signInWithEmailAndPassword(auth, account.email, account.password);

//console.log(res);

if (res) {

//console.log(auth.currentUser.displayName);

props.setStatus("signedIn");

}

setMessage("");


}

catch(error){

setMessage(""+error);

}

}

const changeStatus = function(){

props.setStatus("signUp");

}

return(

<form>

<TextField type = "email" name = "email" value={account.email}

placeholder="電子郵件信箱" label="電子郵件信箱:" onChange={handleChange} autoComplete="email"/><br/>

<TextField type = "password" name = "password" value={account.password}

placeholder="密碼" label="密碼:" onChange={handleChange} autoComplete="current-password"/><br/>

{message}<br/>

<Button variant="contained" color="primary" onClick={handleSubmit}>登入</Button>

<Button variant="contained" color="secondary" onClick={changeStatus}>我要註冊</Button>

</form>

)

}

src/account/SignUp.js

import React, {useState} from 'react';

import {Button, TextField} from '@mui/material';

import { getApps, initializeApp } from "firebase/app";

import { getAuth, createUserWithEmailAndPassword,updateProfile } from "firebase/auth";

import {config} from '../settings/firebaseConfig';


//import { Box } from '@mui/system';


export default function SignUp(props) {

if (getApps().length===0) {

initializeApp(config);

}

const [account, setAccount] = useState({email:"",password:"", displayName:""});

const [message, setMessage] = useState("");

const handleChange = function(e){

setAccount({...account,[e.target.name]:e.target.value})

}

const handleSubmit = async function(){

try {

const auth = getAuth();

const res = await createUserWithEmailAndPassword(auth, account.email, account.password);

//console.log(res);

if (res) {

//console.log(res.user);

await updateProfile(auth.currentUser,{displayName: account.displayName});

}

setMessage("");


}

catch(error){

setMessage(""+error);

}

}

const changeStatus = function(){

props.setStatus("signIn");

}

return(

<form>

<TextField type = "text" name = "displayName" value={account.displayName}

placeholder="姓名" label="姓名:" onChange={handleChange} /><br/>

<TextField type = "email" name = "email" value={account.email}

placeholder="電子郵件信箱" label="電子郵件信箱:" onChange={handleChange} autoComplete="email"/><br/>

<TextField type = "password" name = "password" value={account.password}

placeholder="密碼" label="密碼:" onChange={handleChange} autoComplete="current-password"/><br/>

{message}<br/>

<Button variant="contained" color="primary" onClick={handleSubmit}>註冊</Button>

<Button variant="contained" color="secondary" onClick={changeStatus}>已經註冊,我要登入</Button>

</form>

)

}

登出

登出跟登入、註冊很像,利用firebase提供的signOut()。

signOut(auth)

試試看自己完成。

跟老師的範例比對一下:

src/account/SignOut.js

import React, {useState} from 'react';

import {Button} from '@mui/material';

import { getApps, initializeApp } from "firebase/app";

import { getAuth, signOut } from "firebase/auth";

import {config} from '../settings/firebaseConfig';


//import { Box } from '@mui/system';


export default function SignOut(props) {

if (getApps().length===0) {

initializeApp(config);

}

const [message, setMessage] = useState("");

const handleSubmit = async function(){

try {

const auth = getAuth();

await signOut(auth);

setMessage("");

props.setStatus("signIn");

}

catch(error){

setMessage(""+error);

}

}

return(

<form>

<Button variant="contained" color="primary" onClick={handleSubmit}>登出</Button>

{message}<br/>

</form>

)

}

src/ui/Main.js

import React, {useState} from 'react';


import AppMenu from './AppMenu';

import SignUp from '../account/SignUp';

import SignIn from '../account/SignIn';

import SignOut from '../account/SignOut';


export default function Main() {

const [status, setStatus] = useState("signIn");

return (

<div>

<AppMenu/>

{status==="signUp"?

<SignUp setStatus={setStatus}/>

:status==="signIn"?

<SignIn setStatus={setStatus}/>

:

<SignOut setStatus={setStatus}/>

}


</div>

)


}

將firestore的存取規則改為:

rules_version = '2';

service cloud.firestore {

match /databases/{database}/documents {

match /{document=**} {

allow read: if true;

allow write: if request.auth != null;

}

}

}

當我們登入時,我們可以看到內容,也可以新增內容。但是,如果沒有登入,新增或修改內容時就會被firebase拒絕。當然,看系統的需求,也可以設定為登入才可看到內容。

不過,使用者並不會看到錯誤訊息,記得要回去改程式ProductAddEdit,讓使用者知道新增或修改失敗

const update = async function(){

const db = getFirestore();

setMessage("");

try{

if (action === "新增"){

const docRef = await addDoc(collection(db,"product"),{

desc:product.desc,

price:parseInt(product.price)

});

console.log(docRef.id);

}

else {

await setDoc(doc(db,"product",product.id),{

desc:product.desc,

price:parseInt(product.price)

});

}

props.close();

}

catch(e){

//console.log(e.code);

if (e.code==="permission-denied"){

setMessage("尚未登入!!");

}

}


}

或者,登出後就隱藏相關的按鈕。不過,這就牽涉到不同元件之間分享state變數,會使用到Context

作業

  • 想想看,你們的系統要如何處理登入、登出?

  • 如果是一開始就一定登入,那要怎麼修改你們的程式呢?

  • 要怎麼處理登出?

  • 如何處理自動登入?

  • 如何處理忘記密碼?

參考資料

Firebase