มาทำระบบ member ภายใน 30 นาที ด้วย Firebase Authentication กับ iOS กัน (Swift 3)

Kittisak Phetrungnapha
iTopStory
8 min readOct 3, 2016

--

ในการพัฒนาแอปพลิเคชันที่ไม่ได้เป็น static content มากนัก หรือก็คือ มีการ interact กับ user ยกตัวอย่าง เช่น แอปสั่งซื้อสินค้าออนไลน์ แอปจองโรงแรมที่พัก หรือแอปแชทต่างๆ เป็นต้น feature หนึ่งที่สำคัญมากๆ ก็คือ ระบบ member นั่นเอง เพราะทางผู้พัฒนาแอปจำเป็นต้องรู้ว่าใครเป็นผู้ใช้งานแอปของพวกเขาบ้าง

ลองคิดดูเล่นๆ ว่า ถ้าเราจะทำระบบ member ขึ้นมาสักอันหนึ่ง จะต้องมีอะไรบ้าง? แน่นอนว่าอย่างน้อยๆ จะต้องมีเรื่องของ login, register, manage account เอาง่ายๆ แค่สามอันนี้ก่อน (แต่จริงๆ ต้องมีมากกว่านี้อีก) สิ่งที่เราต้องทำก็คือ server, infrastructure, database, API, back office บลา บลา บลา กว่าเราจะได้เริ่มทำแอปจริงๆ ไม่รู้จะเสียเวลาเท่าไหร่ แถมสมมุติว่ามัน บูม ขึ้นมา จำนวน user หลั่งไหลเข้ามามากมายมหาศาล ต้องมาคิดถึงเรื่อง scalable อีก ถ้าทำไม่ดีระบบก็ล่ม แอปก็เข้าใช้งานไม่ได้อีก ไหนจะเรื่อง security ของระบบเราอีก ทำไม่ดีก็โดน injection กันไป

แต่อย่าเพิ่งท้อใจไปครับ นั่นมันเป็นเรื่องของอดีต เพราะว่าในตอนนี้เรามีตัวช่วยที่แสนจะสะดวกสบายในการสร้างระบบ member ขึ้นมา นั่นก็คือ Firebase Authentication ที่ Google ได้เตรียม SDK ไว้ให้สำหรับนักพัฒนาแล้ว โดยมันทำสิ่งที่เราต้องทำในการสร้างระบบ member ที่ผมได้บอกด้านบนไว้ให้หมดแล้ว หมดห่วงเรื่อง scalable และ security ไปได้เลย แถมยังสามารถเชื่อมกับระบบ social login ยอดนิยมอย่าง Facebook, Google, Twitter ได้อีก และนอกจากนี้ยัง cross platform อีกด้วย มีทั้ง iOS, Android และ Web ที่สำคัญมันฟรีครับ มาลองดูวิดีโอแนะนำคร่าวๆ กันก่อนครับ

วิดีโอแนะนำ Firebase Authentication

โดยเนื้อหาของ Firebase Authentication (ต่อไปจะขอเรียกว่า FIR Auth) จะแบ่งออกเป็น

  1. การติดตั้ง FIR Auth ใน iOS
  2. การสร้างบัญชีผู้ใช้ และการเข้าสู่ระบบ
  3. การจัดการบัญชีผู้ใช้งาน

โดยสามารถโหลด starter project มาก่อน ภายในก็จะประกอบไปด้วยหน้า login กับหน้า profile ครับ เอ่อ ลืมบอกไป ผมใช้ Xcode 8 และเขียนด้วย Swift 3 นะครับ ใครยังไม่มี Xcode 8 ไปหามาด่วนๆ

starter project layouts

Part 1 การติดตั้ง Firebase และ FIR Auth ใน iOS

หลังจากโหลด starter project ขึ้นมาแล้ว เปิด .xcworkspace ขึ้นมาด้วย Xcode 8 แล้วเปลี่ยน bundle id ให้เป็นตามที่ทุกคนต้องการ แล้วทำการติดตั้ง Firebase ให้กับ starter project เสียก่อน

เพิ่ม dependency สำหรับ FIR Auth ใน pod file แล้ว pod install ได้เลยครับ

pod 'Firebase/Auth'

หลังจากนั้นให้เข้าไปที่ https://console.firebase.google.com/ เลือกที่โปรเจคของเรา แล้วก็เลือกเมนู Auth ด้านซ้ายมือ ดังภาพด้านล่าง

หลังจากนั้นให้เราไปที่ SIGN-IN METHOD เพื่อทำการเปิดใช้งานช่องทางต่างๆ ในเข้าสู่ระบบ โดยมีให้เลือกหลายแบบ แต่ในบทความจะเจาะลึกแค่สองแบบก่อน คือ Email/Password และ Anonymous ส่วนถ้าใครจะทำ social login อื่นๆ กันต่อ ก็สามารถมาเปิดในภายหลังได้ ตอนนี้ก็เปิดแค่สองอันตามภาพ ก็เป็นอันเสร็จสิ้น Part 1 แล้ว อย่างง่าย

Part 2 การสร้างบัญชีผู้ใช้ และการเข้าสู่ระบบ

ในการสร้างบัญชีผู้ใช้งาน สามารถสร้างได้สองวิธี คือ

การสร้างบัญชีผู้ใช้ผ่าน Firebase Console

วิธีการคือไปที่https://console.firebase.google.com/ โดยให้เลือกโปรเจคของเรา แล้วเลือก Auth -> USERS -> ADD USER จากนั้นกรอก email และ password ให้เรียบร้อย แล้วก็กด ADD USER อีกที ดังภาพ

หลังจากนั้นจะมี user record ที่เราเพิ่งเพิ่มเข้าไปโผล่มาแล้ว โดยเราสามารถจัดการบัญชีผู้ใช้ได้ผ่านทางนี้ด้วยเช่นกัน เช่น reset password, disable account และ delete account เป็นต้น

การสร้างบัญชีผู้ใช้ผ่าน FIR Auth SDK

ตามปกติเราจะใช้วิธีนี้กัน อารมณ์เหมือนมีปุ่มให้ register แล้ว login ในแอปของเรานั่นเอง เอาล่ะ เรามาลองทำไปพร้อมๆ กันเลย ให้เปิด starter project ที่ติดตั้ง FIR Auth ใน Part 1 เรียบร้อยแล้วขึ้นมา แล้วไปที่ LoginViewController.swift ให้ import SDK เข้ามาก่อน

import FirebaseAuth

เพิ่ม authListener สำหรับคอยตรวจสอบสถานะของ FIRAuth ว่ามีการเปลี่ยนแปลงสถานะหรือไม่ เช่น จากผู้ใช้งานปกติ กลายเป็นผู้ใช้งานที่เข้าสู่ระบบแล้ว

var authListener: FIRAuthStateDidChangeListenerHandle?

ใน viewWillAppear ให้ทำการ subscribe เจ้า authListener ด้านบนซะ

authListener = FIRAuth.auth()?.addStateDidChangeListener({ (auth, user) in
if let _ = user {
self.goToProfilePage()
}
})

ใน viewWillDisappear ให้ทำการ unsubscribe ตัว authListener เมื่อไม่ได้ใช้งานทิ้งด้วย

FIRAuth.auth()?.removeStateDidChangeListener(authListener!)

ไปที่ registerButtonTouch แล้วเพิ่ม code ลงไปตามนี้

FIRAuth.auth()?.createUser(withEmail: usernameTextField.text!, password: passwordTextField.text!) { (user, error) in
if let error = error {
AppDelegate.showAlertMsg(withViewController: self, message: error.localizedDescription)
}
}

จาก code ด้านบนเป็นการเรียก method createUser โดยส่ง email และ password เข้าไปเป็น parameters ถ้า error ก็จะโชว์ alert ขึ้นมาแจ้งสาเหตุ ถ้า success มันก็จะวิ่งเข้า authListener ที่เรา subscribe ไว้ก่อนหน้า แล้วเรียก goToProfilePage เพื่อเปิดไปยังหน้า profile ต่อไป (สำหรับ starter project นี้ ถ้า register สำเร็จ ก็ login ให้อัตโนมัติด้วย) ทีนี้ให้ลองรัน project แล้วกรอก email กับ password ที่ต้องการ หลังจากนั้นกดปุ่ม Register แล้วรอสักครู่ ถ้าไม่มีอะไรผิดพลาด แอปก็จะพาไปหน้า profile ต่อครับ ดังภาพ

สมัครสมาชิกด้วย email

และเมื่อเราเข้าไปดูใน Firebase Console ก็จะพบบัญชีผู้ใช้ที่เราเพิ่งสร้างผ่านแอปของเราเพิ่มเข้ามาแล้ว

การเข้าสู่ระบบด้วย Email และ Password

ไปที่ loginButtonTouch แล้วเปลี่ยน code เป็นตามนี้ สำหรับ login เข้ากับ Firebase

FIRAuth.auth()?.signIn(withEmail: usernameTextField.text!, password: passwordTextField.text!) { (user, error) in
if let error = error {
AppDelegate.showAlertMsg(withViewController: self, message: error.localizedDescription)
}
}

เช่นเดียวกับการ register ถ้า login สำเร็จ ก็จะวิ่งเข้า authListener แล้วไปหน้า profile ต่อ

การเข้าสู่ระบบแบบ Anonymous

เราสามารถให้ผู้ใช้งานเข้าสู่ระบบแบบ anonymous ก่อนได้ แล้วค่อยไปทำการ login เพื่อ mapping ข้อมูลทีหลัง ยกตัวอย่างเช่น แอปพลิเคชันสั่งซื้อสินค้าออนไลน์อาจจะให้ผู้ใช้งานรับชมสินค้าและกดถูกใจสินค้าที่ชอบเอาไว้ก่อนได้ หลังจากนั้นเมื่อเขาต้องการจะสั่งซื้อสินค้าจริงๆ ก็ค่อยแจ้งให้เขา เข้าสู่ระบบ และหลังจากเข้าสู่ระบบสำเร็จแล้ว สินค้าที่เขากดถูกใจไว้ก็ยังคงอยู่เหมือนเดิม ไม่ได้หายไปไหน และสามารถทำรายการต่อได้ทันที

ไปที่ anonymousLoginButtonTouch แล้วเพิ่ม code ด้านล่างเพื่อเข้าสู่ระบบแบบ anonymous

FIRAuth.auth()?.signInAnonymously() { (user, error) in
if let error = error {
AppDelegate.showAlertMsg(withViewController: self, message: error.localizedDescription)
}
}

กดปุ่ม Anonymous Login เมื่อเข้าสู่ระบบสำเร็จ ก็จะวิ่งเข้า authListener เช่นเดียวกัน แต่สิ่งที่แตกต่างจากการเข้าสู่ระบบธรรมดาก็คือ field email จะยังไม่มี ดังภาพ

ส่วนวิธี mapping anonymous account ให้เป็น permanant account สามารถอ่านเพิ่มเติมได้ โดยเลื่อนลงมาในหัวข้อ Convert an anonymous account to a permanent account

หลังจากนี้ถ้าผู้ใช้งานเปิดแอปครั้งต่อไป ก็จะข้ามมาที่หน้า profile เลย เพราะ authListener จะคอยตรวจสอบให้อยู่แล้ว (มันก็ควรจะเป็นแบบนี้แหละ ใครจะมาอยากกรอกข้อมูลเพื่อล้อกอินทุกครั้งกันล่ะ??)

ส่วนถ้าใครอยากจะใช้ social login เข้ากับ Firebase ก็ตามไปอ่านเพิ่มเติมได้ที่

Facebook https://firebase.google.com/docs/auth/ios/facebook-login

Google-Signin https://firebase.google.com/docs/auth/ios/google-signin

Twitter https://firebase.google.com/docs/auth/ios/twitter-login

GitHub https://firebase.google.com/docs/auth/ios/github-auth

Part 3 การจัดการบัญชีผู้ใช้งาน

FIR Auth มีระบบหลายอย่างที่ช่วยให้ผู้ใช้งานสามารถจัดการกับบัญชีตัวเองได้อย่างสะดวก แต่ก่อนที่จะไปลงรายละเอียดกัน ขอให้ทุกคนไปเพิ่ม code เพื่อแสดงข้อมูล user profile กันเสียก่อน

ไปที่ ProfileViewController.swift แล้ว import SDK เข้ามา

import FirebaseAuth

แล้วใน viewDidLoad เพิ่ม code ชุดนี้เข้าไป

if let user = FIRAuth.auth()?.currentUser {
setUserDataToView(withFIRUser: user)

if user.isAnonymous {
AppDelegate.showAlertMsg(withViewController: self, message: "You are an Anonymous. If you want to update the profile, you have to login first.")
manageProfileBarButton.isEnabled = false
}
else if !user.isEmailVerified {
AppDelegate.showAlertMsg(withViewController: self, message: "Your account is not verified. Please select manage to verify it.")
}
} else {
let alert = UIAlertController(title: "Message", message: "No user is signed in", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: { (action: UIAlertAction) in
self.logout()
})
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)
}

จากนั้นเพิ่ม setUserDataToView เอาไว้สำหรับ set ค่าต่างๆ ให้กับ label

func setUserDataToView(withFIRUser user: FIRUser) {
providerIDValueLabel.text = user.providerID
uidValueLabel.text = user.uid
emailValueLabel.text = user.email
nameValueLabel.text = user.displayName
photoUrlValueLabel.text = user.photoURL?.absoluteString
}

อธิบายสั้นๆ ก็คือ ถ้ามาที่หน้า profile แล้วก็จัดการ set ข้อมูลต่างๆ ของ user ให้กับ label ซะ โดยถ้า login anonymous เข้ามา หรือ email ที่ login เข้ามายังไม่ถูก verified ก็จะขึ้น alert แจ้งเตือนให้ user ทราบ

เอาล่ะ ได้เวลาลงรายละเอียดการจัดการบัญชีผู้ใช้งานแล้ว ลุยยย

การ Reset รหัสผ่าน

เพื่อความปลอดภัย หลายคนจึงนิยมใช้รหัสผ่านแต่ละบริการแตกต่างกันไป จึงทำให้เกิดปัญหาลืมรหัสผ่าน และเข้าสู่ระบบไม่ได้ ซึ่ง FIR Auth ครอบคลุมถึงเรื่องการ reset รหัสผ่านไว้ด้วย

เปิด LoginViewController.swift แล้วไปที่ resetPasswordButtonTouch จากนั้นเพิ่ม code ชุดนี้เข้าไปใน closure ของ confirmAction

FIRAuth.auth()?.sendPasswordReset(withEmail: textField.text!) { error in
if let error = error {
AppDelegate.showAlertMsg(withViewController: self, message: error.localizedDescription)
} else {
AppDelegate.showAlertMsg(withViewController: self, message: "Password reset email was sent")
}
}

รันโปรเจค กด Reset Password แล้วกรอก email ที่ต้องการให้ส่งลิ้งสำหรับ reset password ไป แล้วก็กด confirm อีกรอบ

หลังจากนั้นไปที่ mail box ของเรา จะพบว่ามี email มาให้ reset password แล้ว

เมื่อกดที่ link แล้วจะเจอหน้าให้กรอกรหัสผ่านใหม่ในรูปแบบ Google form กรอกเสร็จ กด save แล้วลอง login เข้าแอปด้วยรหัสผ่านใหม่กันเลย สะดวกจริงๆ

การอัพเดท display name และ image profile

โดยในตอนแรก display name และ image profile จะยังไม่มี โดยเราสามารถ update ได้ โดยเพิ่ม code ไปที่ ProfileViewController.swift ใน changeUserInfo โดยให้เพิ่มเฉพาะใน closure ของ confirmAction ตามนี้

if let user = FIRAuth.auth()?.currentUser {
let changeRequest = user.profileChangeRequest()

changeRequest.displayName = nameTextField.text
changeRequest.photoURL = NSURL(string: imageTextField.text!) as? URL
changeRequest.commitChanges { error in
if let error = error {
AppDelegate.showAlertMsg(withViewController: self, message: error.localizedDescription)
} else {
AppDelegate.showAlertMsg(withViewController: self, message: "Your profile was updated")
self.setUserDataToView(withFIRUser: user)
}
}
}

build และ run โปรเจคใหม่อีกครั้ง หลังจากนั้นกดที่ manage ในหน้า profile แล้วเลือกที่ Change name and image แล้วให้กรอกชื่อ display name ส่วน image profile url ก็ให้ใส่ image url ของเราเข้าไป (ผมขี้เกียจให้ทุกคนมากรอก ก็เลยใส่ให้ไปเลยเป็น example สำหรับ image url) หลังจากนั้นก็กด confirm

จะเห็นว่ามี display name กับ photo url โผล่มาแว้ว ส่วนถ้าใครอยาก upload รูปเข้าไป อาจจะต้องเอาไปผูกกับเรื่อง Firebase Storage อีกที

การเปลี่ยนรหัสผ่าน

ไปที่ changePassword แล้วเพิ่ม code ใน closure ของ confirmAction

let user = FIRAuth.auth()?.currentUser
user?.updatePassword(textField.text!) { error in
if let error = error {
AppDelegate.showAlertMsg(withViewController: self, message: error.localizedDescription)
} else {
AppDelegate.showAlertMsg(withViewController: self, message: "Password was updated")
}
}

กรอกรหัสผ่านอันใหม่เข้า กด confirm

แต่มีข้อยกเว้นว่า ในกรณีที่จะเปลี่ยน password ต้อง login เข้ามาไม่นานมากนัก (ประมาณไม่เกิน 10 นาที) มิฉะนั้นจะขึ้นข้อความเหมือนรูปขวาบน ซึ่งต้องทำการ reauthenticate ใหม่อีกรอบ โดยสามารถอ่านรายละเอียดได้ที่

แต่ในบทความนี้เพื่อไม่ให้ยาวเกินไป จะใช้วิธี logout แล้ว login เข้ามาใหม่แทนไปก่อน ให้เพิ่ม code ใน logout ดังนี้

try! FIRAuth.auth()!.signOut()

หลังจากนั้น run โปรเจค แล้วกด logout แล้ว login มาใหม่ จากนั้นให้ลองไปเปลี่ยนรหัสผ่านอีกครั้ง ก็จะได้แล้ว

การ verified email

ในบางครั้ง apps เราอาจจะมี flow ที่ต้องการให้ verified email ก่อนทำรายการสำคัญๆ เช่น การจ่ายเงิน เป็นต้น ซึ่ง Firebase ก็มีเรื่องการ verified email ให้ด้วย ให้ไปที่ sentVerifiedEmail แล้วเพิ่ม

if let user = FIRAuth.auth()?.currentUser {
user.sendEmailVerification() { error in
if let error = error {
AppDelegate.showAlertMsg(withViewController: self, message: error.localizedDescription)
} else {
AppDelegate.showAlertMsg(withViewController: self, message: "Email verification has been sent to [\(user.email!)]. Please check your email and verify it. Then login again.")
self.logout()
}
}
}

หลังจากให้เพิ่ม menu สำหรับการ verified ให้กับ action sheet เพิ่ม โดยไปที่ manageProfile แล้วเพิ่ม code ชุดนี้ลงไประหว่าง manageActionSheet.addAction(changePasswordAction) กับ manageActionSheet.addAction(changeEmailAction)

if let user = FIRAuth.auth()?.currentUser, !user.isEmailVerified {
let verifyAccountAction = UIAlertAction(title: "Verify Account", style: .default) { (action: UIAlertAction) in
self.sentVerifiedEmail()
}
manageActionSheet.addAction(verifyAccountAction)
}

หลังจากนั้นไปที่ mail box ของเรา

จะได้ link สำหรับกด verified email มา กดเลย

หลังจากนั้นให้ login เข้าที่ apps ใหม่อีกครั้ง จะพบว่า จะไม่มี alert ขึ้นมาบอกว่า email ยังไม่ถูก verified อีกต่อไปแล้ว

การเปลี่ยน email หลักสำหรับการเข้าสู่ระบบ

เราสามารถเปลี่ยน email ที่ใช้เข้าสู่ระบบได้ เพราะ FIR Auth ไม่ได้เอา email เป็น primary key เพียงแต่ว่าห้ามไปซ้ำกับ email ที่มีอยู่ในระบบอยู่แล้ว ให้เพิ่ม code เข้าไปที่ changeEmail ใน clorsure ของ confirmAction ดังนี้

let user = FIRAuth.auth()?.currentUser
user?.updateEmail(textField.text!) { error in
if let error = error {
AppDelegate.showAlertMsg(withViewController: self, message: error.localizedDescription)
} else {
AppDelegate.showAlertMsg(withViewController: self, message: "Email was updated. You have to login again.")
self.logout()
}
}

เช่นเดียวกันกับการเปลี่ยนรหัสผ่าน ถ้า login เข้ามานานเกิน ต้องทำการ reauthenticate ด้วย

เมื่อเข้าไปดูใน Firebase Console จะพบว่า email เราถูกเปลี่ยนไปแล้ว โดยสามารถใช้ email ใหม่ กับรหัสผ่านเดิมเข้าสู่ระบบได้เลย (ต้องทำการ verified email ใหม่ด้วยนะ)

การ Delete Account

user สามารถที่จะลบ account ของตนเองได้ ไปที่ deleteAccount แล้วเพิ่ม code ไปที่ closure ของ confirmAction

if let user = FIRAuth.auth()?.currentUser {
let alert = UIAlertController(title: "Delete Account", message: "[\(user.email!)] will be deleted. This operation can not undo. Are you sure?", preferredStyle: .alert)

let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: nil)
let confirmAction = UIAlertAction(title: "Confirm", style: .default) { (action: UIAlertAction) in
user.delete { error in
if let error = error {
AppDelegate.showAlertMsg(withViewController: self, message: error.localizedDescription)
} else {
AppDelegate.showAlertMsg(withViewController: self, message: "[\(user.email!)] was deleted")
self.logout()
}
}
}

alert.addAction(cancelAction)
alert.addAction(confirmAction)
self.present(alert, animated: true, completion: nil)
}

เช่นเดียวกับการ change password และ change email ถ้า login มานานเกินไป จะต้องทำการ reauthenticate ด้วย

เมื่อเข้าไปตรวจสอบที่ Firebase Console ก็จะไม่พบ email ที่เพิ่งลบไปแล้ว

เป็นอันจบไปเรียบร้อยแล้วนะครับ สำหรับระบบบริหารจัดการ member โดยใช้ Firebase Authentication เข้ามาช่วย เป็นไงครับ เกิน 30 นาทีไหมครับ อิอิ และสามารถเข้าไปอ่านรายละเอียดแบบจัดเต็มได้ที่

โดย project ตัวเต็มสามารถโหลดได้ที่ https://github.com/itopstack/Firebase-iOS-Authentication-Demo ให้ทุกคนลองเอาไปเล่นกันได้เลย แต่แนะนำว่าให้เปลี่ยน bundle id และไปสมัคร project ใน Firebase Console แล้วก็เปลี่ยน GoogleService-Info.plist ให้เป็นของตัวเองด้วยจะดีกว่า จะได้ไม่ต้องกรอก email มาให้ผมไงครับ อิอิ

หวังว่าบทความนี้จะเป็นประโยชน์สำหรับพวกเราชาว iOS Dev กันไม่มากก็น้อยนะครับ ถ้ามีข้อผิดพลาดประการใด ขออภัยมา ณ ที่นี้ด้วยครับ สำหรับวันนี้ ลากันไปก่อน พบกันใหม่ บทความหน้าครับ สวัสดีครับ :)

ติดตามเรื่องราวต่างๆ ทั้งเทคโนโลยี มุมมองชีวิต การเรียนรู้ การใช้ชีวิต ได้ที่ https://www.facebook.com/itopstory/

--

--

Kittisak Phetrungnapha
iTopStory

I am a software engineer who fall in love to code, read, and write. :) itopstory.com