Firebase Authentication

เราจะมาเริ่มเข้าสู่กระบวนการ Authentication ของจริงกันเลย โดยเราจะเริ่มต้นจาก

Log In process

ก่อนอื่น เราเริ่มต้นกันด้วย flow ของการ log in ก่อนเลย

signinflow

flow นี้ จะเริ่มต้นด้วยการ log-in หาก log in fail ระบบจะให้ register account ใหม่ ซึ่งหาก register fail อีก ระบบจะแสดง error บนหน้าจอให้

จากนั้นให้เปิด src/components/LoginForm.js ขึ้นมาแล้วใส่ code เข้าไปดังนี้

หากยังจำกันได้จาก tutorial ใน ES6 http://www.forminit.com/es6-react-end/ เวลาเราเขียน javascript class แบบ ES6 เวลาใช้งาน this ต้องทำการ bind ให้ชัดเจน เพราะมันจะไม่ binding ให้เรา ดังนั้น สำหรับ onChangeText เราจึงต้องเขียน code แบบนี้

นั่นคือ เราต้อง bind this ของ class ยิงเข้าไปใน callback ที่ชื่อว่า onButtonPress ด้วยตัวเอง

เขียน Code ตาม Flow สาย fail

จาก sign in flow ในตอนต้นของบทความ เราจะมาเริ่มเขียน code ทางฝั่ง log in fail โดยให้เปิดไฟล์ src/components/LoginForm.js ขึ้นมาแล้วแก้ไฟล์ดังนี้

ภายในฟังก์ชั่น onButtonPress เราได้เรียกใช้ฟังก์ชั่น signInWithEmailAndPassword ซึ่งเป็นฟังก์ชั่นที่จะ return promise กลับมา ดังนั้น เราสามารถใช้ .then ในการรองรับค่าในฝั่ง success และใช้ .catch เพื่อรองรับค่าในฝั่ง fail ดังนั้น หากจำ code ข้างต้นมา map กับ flow จะเป็นดังนี้

screen-shot-2559-11-28-at-12-35-14-pm

.catch แรกจะใช้ในกรณี sign in fail จากนั้นก็จะทำการสร้าง account ใหม่ ด้วยฟังก์ชั่น createUserWithEmailAndPassword จากนั้นใน catch ตัวที่สองจะรองรับในกรณีที่การสร้าง account ใหม่ เกิด fail ขึ้นมา

Troubleshooting

สำหรับคนที่ใช้ React Native 0.38 ขึ้นไป เวลาใช้พวก TouchableOpacity แล้วเวลาเรากดปุ่มใน emulator อาจจะเจอกับ warning ดังต่อไปนี้

ปัญหานี้เกิดจากเมื่อใดก็ตามที่เราใช้ component พวก tochable… ทั้งหลายภายใน IOS มันจะต้องการ RCTAnimation module ซึ่งตอนที่เราสร้าง react native project มันไม่ได้รวม module ตัวนี้เข้าไปใน ios project ด้วย (คาดว่า react native เวอร์ชั่นถัดไปจะแก้ปัญหาตรงนี้)

ดังนั้น วิธีแก้ปัญหาคือ ให้เรา add module ตัวนี้เข้าไปด้วยตัวเอง ด้วยขั้นตอนดังนี้

  1. เปิด xCode แล้วเปิดโปรเจ็กท์ auth.xcodeproj ที่ folder auth/iosscreen-shot-2559-11-28-at-8-11-34-pm
  2. import RCTAnimation เข้ามาโดยเลือก File -> Add Files to “auth”…screen-shot-2559-11-28-at-8-14-59-pm
  3. ให้เลือกไฟล์ auth/node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeprojscreen-shot-2559-11-28-at-8-27-15-pm
  4. จากนั้นให้ลาก RCTAnimation.xcodeproj ไปวางไว้ใน Libraries ของ xCodescreen-shot-2559-11-28-at-8-31-19-pm
  5. ตรง project pane ด้านซ้าย ให้คลิกที่ชื่อโปรเจ็กท์ (auth) แล้วตรงกลางให้คลิกเลือก Build Phases screen-shot-2559-11-28-at-8-35-55-pm
  6. จากนั้นให้ลากไฟล์ libRCTAnimation.a จาก Products ที่อยู่ภายใต้ RCTAnimation.xcodeproj มาวางไว้ใน Link Binary With Libraries screen-shot-2559-11-28-at-8-40-38-pm
  7. จากนั้นให้เลือกเมนู Product -> Clean แล้วต่อด้วย Product -> Build
  8. ปิด xCode แล้วเข้าไปที่ terminal ณ root project เพื่อรันคำสั่ง

Flow testing

คราวนี้เราอยากจะรู้ว่าเวลาที่เราทำการ sign in และ sign up แล้วมันเกิด fail ขึ้นมา มัน fail เพราะสาเหตุใดกันแน่

ให้ทำการดัก console.log เข้าไปใน LoginForm.js ดังนี้

คราวนี้ให้เราลองเปิด emulator แล้วทำการ refresh จากนั้นเปิด JS debugger (อ้อ ลืมไป เปิด js debugger ทีไร ให้ปิดทุกครั้งด้วยนะครับ ไม่งั้นรัน emulator คราวหน้าอาจเจอ error) แล้วให้ลองพิมพ์ email และ password ที่มีจำนวนตัวอักษรไม่เกิน 4 ตัว

screen-shot-2559-11-28-at-9-09-55-pm

จะเห็นว่า message ของ error แจ้งให้เรารู้ว่า there is no user… และเมื่อจะลง sign up  ให้ก็ไม่ผ่าน เพราะ password มีจำนวน character ไม่ถึง 6 ตัว

คราวนี้ให้ลองใหม่ โดยกรอก password จำนวน 6 ตัวขึ้นไป มันจะทำการ sign up ให้กับ user ของเรา เมื่อเข้าไปดูที่ firebase’s console เราจะเห็น user ใหม่เกิดขึ้นมา

screen-shot-2559-11-28-at-9-22-20-pm

คราวนี้ให้เราลอง sign in ด้วย user คนเดิม แต่แกล้งกรอก password ผิด มันจะขึ้นคำว่า Authentication Failed. ขึ้นมา ซึ่งเป็นสิ่งที่ถูกต้อง

แต่สิ่งที่ไม่ถูกต้องก็คือ เวลาเราเปลี่ยน password แล้วกดปุ่ม log in อีกครั้ง ไม่ว่าจะ success หรือ fail ก็ตาม คำว่า Authentication Failed. ก็จะไม่หายไปไหน

ให้เราแก้ปัญหาโดยเพิ่ม code เข้าไปใน LoginForm.js ดังนี้

ซึ่งแปลว่า ทุกครั้งที่ onButtonPress ถูกเรียกใช้ มันจะทำการ set error state ให้มีค่าเท่ากับ empty string ทุกครั้ง

Spinner component

ทุกครั้งที่เราคลิก log in จะต้องมีช่วงเวลาที่เราต้องรอซึ่งเราควรจะแสดง spinner ขึ้นมาเป็น feedback ให้ user ได้รับรู้

ให้เราสร้าง Spinner.js เข้าไปที่ auth/src/components/common/Spinner.js ซึ่งจะทำให้ตอนนี้โครงสร้างไฟล์ของเราเป็นดังนี้

ให้ใส่ code เข้าไปดังนี้

ในที่นี้เราจะใช้ build-in component ของ react-native ทื่ชื่อว่า ActivityIndicator มาทำหน้าที่เป็น spinner โดยตัว ActivityIndicator นั้นจะมี property size ใช้สำหรับกำหนดขนาดของ spinner ซึ่งสามารถระบุได้สองค่า คือ small และ large โดยเรากำหนดค่า default ไว้เป็น large

จากนั้นให้เปิด ไฟล์ src/components/common/index.js ขึ้นมาแล้วแก้ code ดังนี้

จากนั้นก็ให้นำ Spinner component ตัวนี้ไปใช้กับ LoginForm โดยให้เปิดไฟล์ src/components/LoginForm.js ขึ้นมาแล้วแก้ code ดังนี้

เราเพิ่ม state ใหม่เข้าไปหนึ่งตัวชื่อว่า loading ใช้สำหรับเช็คว่า ตอนนี้ระบบกำลัง load อยู่หรือไม่ โดยให้ค่า default เป็น false แต่หากเมื่อใดที่มีการกดปุ่ม login ค่า loading จะเปลี่ยนเป็น true ซึ่งหมายความว่า ตัว Spinner จะปรากฎขึ้นมาบนหน้าจอ

ใน <CardSection> เราได้เปลี่ยนจาก <Button> ให้มาเรียกใช้ function ที่ชื่อว่า renderButton() แทน โดยฟังก์ชั่นนี้มีหน้าที่ในการเลือกว่าจะ render component ตัวไหนระหว่าง <Spinner> หรือ <Button> โดยตัดสินจากค่าของ loading state นั่นเอง

ให้เราลอง refresh emulator แล้วกดปุ่ม log in ดู ก็จะเห็นว่ามี spinner ปรากฎตัวขึ้นมา

screen-shot-2559-11-28-at-10-09-26-pm

LoginForm refactoring

คราวนี้เราจะทำการ refactoring LoginForm เพื่อให้ code ดูดีขึ้น และจะทำการปรับ code ให้มีการ clear Spinner ออกจากหน้าจอในกรณีทำกิจกรรมเสร็จเรียบร้อยแล้วไปพร้อมๆ กัน

ให้เปิดไฟล์ src/components/LoginForm.js ขึ้นมาแล้วแก้ code ดังนี้

สำหรับตอนนี้ ในกรณีที่ login success เราจะสร้าง .then() ขึ้นมารองรับ case นี้ (signInWithEmailAndPassword return promise ออกมา ดังนั้น เราสามารถใช้ then รอรับได้เลย) โดยในที่นี้เราจะสร้างฟังก์ชั่น onLoginSuccess เอาไว้ โดยมีจุดประสงค์เพื่อรีเซตค่าของ state ทั้งหมดบนหน้าจอ

ส่วนในกรณี login fail ก็มีการสร้างฟังก์ชั่น onLoginFail ไว้เช่นกัน โดยมีจุดประสงค์เพื่อแสดง error message และเคลียร์ Spinner ออกจากหน้าจอ

จับสถานะการ log in ของ user

เนื่องจากตอนนี้ เวลาที่ user log in เข้ามาในระบบ เรายังไม่ได้ทำอะไรเพิ่มเติมเลย แต่ก่อนที่เราจะสร้าง component ใดๆ เพื่อการนี้ เราต้องมาเตรียม state เพื่อใช้ตรวจสอบสถานะการ log in ของ user แต่ละคนเสียก่อน

ให้เปิดไฟล์ src/App.js ขึ้นมา แล้วเพิ่ม code ของ firebase เข้าไปดังนี้

ในที่นี้เราได้สร้าง state ขึ้นมาชื่อว่า loggedIn เพื่อใช้เก็บสถานะการ log in ของ user เอาไว้ โดยให้ค่า default เป็น false

Firebase authentication นั้นได้เตรียม event handler function มาตัวหนึ่งชื่อว่า onAuthStateChanged ซึ่งเป็นฟังก์ชั่นที่จะถูกเรียกทุกครั้งเมื่อมีการ log in และ log out โดยฟังก์ชั่นนี้จะรับ argument คือ user ซึ่งหากเมื่อใดที่ user มีข้อมูลก็แปลว่า มีการ log in เข้ามา แต่หาก user มีค่าเป็น undefined ก็แปลว่า เขาได้ log out ออกไปแล้ว

ปิดจ๊อบ

ตอนนี้เราเหลือ feature สุดท้ายแล้วสำหรับ auth project นั้นคือการ log out ซึ่ง Firebase ได้เตรียมฟังก์ชั่นไว้ให้เราแล้วนั่นคือ firebase.auth().signOut() (ง่ายมะ)

ให้เราเปิดไฟล์ src/App.js ขึ้นมาแล้วแก้ code ดังนี้

จุดสำคัญของ code ข้างบนก็คือ variable ที่เป็น boolean ใน javascript นั้นมีค่าได้สามค่า กล่าวคือ true, false และ null ดังนั้น และในที่นี้เราต้องการจะให้มีการโหลด spinner ในตอนช่วงระหว่างที่ยังไม่เสร็จสิ้นการ sign-in และ sign-out ดังนั้น เมื่อใดก็ตามที่ state.loggedIn มีสถานะเป็น null หน้าจอจะ render Spinner ขึ้นมา

ซึ่งการเขียน code ใน App.js ก็ใช้มุกเดิมนั่นคือ ตรงฟังก์ชั่น render เราจะใช้วิธีในการทำ conditiona render ซึ่งกำหนดโดยสถานะของ state.loggedIn หากมีค่าเป็น true หน้าจอจะแสดงปุ่ม log out หากเป็น false จะแสดงหน้า LoginForm และหากเป็นค่า null ก็จะแสดงตัว spinner

หากเราลอง refresh emulator บางคนอาจจะเห็นว่า มันจะแสดงหน้าที่มีปุ่ม Log Out เลย ซึ่งนี่คือความพิเศษของ firebase authentication ที่จัดการ session ของ user ให้เราอัตโนมัติ ใครที่ยังไม่ได้ log out มันก็จะคง session ของ user คนนั้นค้างไว้ ทำให้ user ที่เราเพิ่ง log in ไปเมื่อช่วงก่อนหน้า จึงคงสถานะ log in ไว้เช่นเดิม

screen-shot-2559-11-28-at-11-08-34-pm

สำหรับผู้ที่ต้องการ code ต้นแบบ สามารถเข้าไปดูได้ที่

https://github.com/himaeng/auth.git

สรุป

เสร็จสิ้นไปอีกหนึ่งโปรเจ็กท์ย่อย โดยโปรเจ็กท์นี้จะสอนเรื่องการใช้งาน Firebase Authentication ตั้งแต่การ setup ไปจนถึงการใช้งานจริง ทั้งการ signup, log-in และ log-out

ใน tutorial ตอนหน้า เราจะมาคุยกันเรื่อง Redux ซึ่งเป็นเรื่องใหญ่เรื่องนึงทีเดียว ดังนั้น ระวังให้ดีเพราะเนื้อหาคงเยอะพอสมควร 555