“Flutter” x “Firebase” (Authentication)

Siratee K.
Firebase Thailand
Published in
11 min readApr 11, 2020

--

Note: Dependency firebase_auth Version. 0.18.0 ขึ้นมา มีการเปลี่ยนแปลงของ Dependency หลายอย่าง เดียวผมจะมาอัพเดทบทความนี้ให้เป็น Version ล่าสุดอีกครั้งครับ (ในบทความนี้ใช้เป็น Version ^0.15.5+3)

{Intro}

สวัสดีชาว Firebase และชาว Flutter ทุกคนด้วยคับบ กลับมาอีกครั้งกับ Topic Flutter x Firebase วันนี้มาในหัวข้อของ Firebase Authentication. ซึ่งทุกคนก็น่าจะทราบว่าระบบ Login นั้นเป็นพื้นฐานของ App แทบจะทุก App เลย แต่การพัฒนาระบบ Verify Credential นั้นก็ทำเอานักพัฒนาอย่างเราๆเสียเวลาเอามากเช่นกัน. Firebase Authentication จึงเดินเข้าเป็นพระเอกในงานนี้กันเลยทีเดียว เพียงแค่ Setup ไม่กี่ขั้นตอน ก็สามารถมีระบบ Login ที่ปลอดภัยและรวดเร็วได้ ถ้าพร้อมแล้ว มาเริ่ม code กันเลย.

{Prepare your project}

ก่อนอื่นเลย เพื่อนๆต้องมี Flutter Project ที่มี Firebase Profile Setup ไว้อยู่แล้ว สามารถดูวิธีการ Setup Firebase ได้ในบทความแรกของ Flutter x Firebase ในหัวข้อ {Before Developing} ได้เลยครับ

สิ่งที่แตกต่างจากในบทความแรกคือ ใน Project นี้จะใช้ Dependency ชื่อ firebase_auth ให้เราเปลี่ยนมาลง Dependency นี้แทนในบทความแรกครับ

{Start Developing}

ก่อนอื่นมาพูดถึง Scope ของตัวอย่างการพัฒนาของเรากันก่อนดีกว่า. Firebase Authentication นั้นมี Sign-in Method ให้เราถึง 12 Sign-in Method ด้วยกัน ซึ่งถ้าให้ผมเขียนแนะนำทุกอัน กว่าบทความนี้จะได้ปล่อยคงอีกนาน 55555 ดังนั้น ผมจะหยิบยกมาเป็นตัวอย่าง 3+1 Sign-in Method ที่เป็นที่นิยมนั้นก็คือ Sign-in with Email/Password , Sign-in with Google, Sign-in with Apple และ พิเศษด้วย Custom Authentication กับ Sign-in with LINE (LINE Login v 2.1)

P.S (ประสบการส่วนตัว) Sign-in with Apple นี้ต้องดอกจันไว้เลย เพราะหากในเเอพของคุณมีการใช้ Third-Party Sign-In อื่นๆแต่ไม่มี Sign-in with Apple บอกเลยว่าการนำแอพของคุณลง AppStore จะไม่ใช่แค่การกด Submit และรอวัน Publish อย่างเท่ๆแน่นอน ผมลองมาแล้ว แม้แต่แอพเพื่อการศึกษา บอกเลยว่าไม่รอด 5555+

ก่อนเริ่มอันแรก แนะนำโหลด Source Code จาก Git Repo ของผมและดูไปพร้อมกันครับ จะได้เข้าใจมากขึ้น

-> Sign-in With Email/Password

อันนี้น่าจะเป็น Basic ของ Sign-in Method ที่เมื่อนึกถึงระบบ Login ก็ต้องนึกถึงการใส่ Email และ Password แน่นอน มาเริ่มกันเลยดีกว่า

อันดับแรก ไป Enable Sign in Provider Email/Password ที่ Firebase Console ในหัวข้อ Firebase Authentication แทบ Sign-in Method กันก่อนเลย

เมื่อ Enable แล้ว อย่าลืมกด Save ด้วย ถ้าลืมนี่ฮาเลยนะ 555+

คราวนี้กลับมาที่ Flutter Project ของเรา มาสร้างหน้า Login ให้กับ User ของเรากัน โดยจะต้องมีช่องใส่ Email และ Password ซึ่งผมจะออกแบบไว้คร่าวๆประมาณนี้

เนื่องจากการเข้าสู่ระบบด้วย Email นั้นจะต้องทำการ Register ก่อน มิเช่นนั้น จะเกิดเป็น Error PlatformException ERROR_USER_NOT_FOUND ดังนั้นเราจึงต้องมีหน้า Register ซึ่งผมก็สร้างไว้เป็นที่เรียบร้อยแล้ว ประมาณนี้ครับ

มาเริ่มกันที่ Code ในส่วนของการ Register ก่อนดีกว่า

ก่อนอื่นเราต้องเรียก Dependency firebase_auth ขึ้นมาก่อน

import 'package:firebase_auth/firebase_auth.dart';

และประกาศตัวแปรที่จะไปเรียก FirebaseAuth.instance มาเพื่อให้ใช้งานได้ง่าย ผมจะประกาศไว้ในตัวแปรที่ชื่อ _auth ซึ่งหน้าตาก็จะประมาณนี้ครับ

final FirebaseAuth _auth = FirebaseAuth.instance;

ต่อไปการสร้าง User Account ใน Firebase Authentication สามารถทำได้โดยเรียกคำสั่ง .createUserWithEmailAndPassword(email: “”, password: “”) ใน FirebaseAuth.instance ซึ่งเราประกาศไว้อยู่ในตัวแปรชื่อ _auth เรียบร้อยแล้วการเรียกก็จะเป็น

_auth.createUserWithEmailAndPassword(email: email, password: password)

โดยที่คำสั่งนี้จะ Return ค่าที่เป็น Future ออกมา (พูดง่ายๆคือ Promise ใน Javascript นั้นเอง) ดังนั้นเราสามารถใช้ .then() และ .catchError() เพื่อ Handle ค่าต่อได้เลย โดยตัวอย่างในบทความนี้ผมจะทำเป็น Function ไว้จะได้เรียกใช้งานง่ายๆ หน้าตาเป็นแบบนี้ครับ

เมื่อสร้าง User Account เสร็จแล้ว ก็ต้องกลับมาที่หน้า Login ซึ่งการ Login นั้นจะใช้คำสั่งว่า .signInWithEmailAndPassword(email: “”, password: “”)

_auth.signInWithEmailAndPassword(email: email, password: password)

เหมือนกัน คำสั่งนี้จะ Return ค่า Future ออกมา ก็จัดการ Handle .then() และ .catchError() ให้เรียบร้อย จากนั้นก็ยัดใส่ Function จะได้เรียกง่ายๆ ก็จะได้

ซึ่งหากสังเกตุใน .catchError จะเห็นว่าผมมีการ Handle Error ต่างๆไว้ด้วย​โดย Error ที่จะมีโอกาสเกิดขึ้นจากการ Signin จะมี 6 Code ด้วยกัน โดยจะถูก Return มาด้วย PlatformException ซึ่งมีดังนี้

  • ERROR_WRONG_PASSWORD — เมื่อ Password ผิด ก็จะได้รับ Code นี้ครับ
  • ERROR_INVALID_EMAIL — เมื่อ Email ที่ส่งไปให้ Firebase Auth มี Format ไม่ถูกต้อง
  • ERROR_TOO_MANY_REQUESTS — เมื่อ User คนนี้มีการเข้าสู่ระบบหลายครั้งเกินไป จะถูก Disable การ Sign-in
  • ERROR_USER_NOT_FOUND — เมื่อ Email / User นี้ยังไม่ได้ Register กับเรา
  • ERROR_USER_DISABLED — เมื่อ User ถูก Disable ไว้ ไม่สามารถเข้าสู่ระบบได้ (ถ้าภาษาเกมก็คือถูก Ban นั้นเอง)
  • ERROR_OPERATION_NOT_ALLOWED — ถ้าลืม Enable Email/Password Provider ในขั้นตอนแรกใน Firebase Console หรือลืมกด Save. Error นี้คือคำตอบครับ 5555+

-> Sign-in with Google

มาต่อกันที่ Sign-in with Google กันเลย ก่อนอื่นอยากให้มองภาพให้ออกกันก่อน การ Sign-in with Google ด้วย Firebase Authentication นั้น เป็นการเชื่อมต่อกันระหว่างกัน 2 ระบบ (ซึ่งส่วนใหญ่จะเป็นแบบนี้อยู่แล้ว) คือ Google Sign-in เมื่อเข้าสู่ระบบสำเร็จเราจะได้ Credential มา และเราต้อง Pass Credential นั้นต่อให้ Firebase Authentication ต่ออีกที เพื่อทำ Verification กับ Firebase Authentication

อันดับแรกต้องไป Enable Sign-in Provider กันก่อนเลย

หากตรงนี้เรายังไม่ได้ตั้ง Public-Facing Name และ Support email มันจะขึ้นมาให้เราตั้ง ซึ่งต้องตั้งก่อน ไม่งั้นจะ Save ไม่ได้

ต่อไปมาลง Dependency ที่ชื่อว่า google_sign_in ใน Flutter Project ของเรา

ต่อมาต้องมาเจาะลึกที่ละ Platform กันเลย เพราะการ Config แต่ละ Platform ไม่เหมือนกัน

IOS:

อันดับแรกเปิด File ชื่อ Runner.xcworkspace ใน Folder ios ขึ้นมา

ต่อมาเปิดไฟล์ GoogleService-Info.plist ขึ้นมาจะมี Key ชื่อ REVERSED_CLIENT_ID ให้เรา Copy Value ออกมา

และไปที่ File Info.plist ให้เราเพิ่ม Key ตามนี้ หรือทำตาม Gif ด้านล่างได้เลย

<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<!-- Copied from GoogleService-Info.plist key REVERSED_CLIENT_ID -->
<string>Copy REVERSED_CLIENT_ID</string>
</array>
</dict>
</array>

เป็นอันเสร็จฝั่ง IOS

Android:

สิ่งที่เราต้องทำคือเอา SHA Certificate Fingerprint ไปใส่ใน Firebase Console และ ติดตั้ง google-services.json ใหม่

อันดับแรกเราต้องเอา Certificate Fingerprint SHA1 มาก่อน โดยสามารถใช้คำสั่งจากนี้เพื่อเอา SHA Cert. Finger Print ออกมาได้เลย

  • To get the release certificate fingerprint:
keytool -exportcert -list -v \
-alias <your-key-name> -keystore <path-to-production-keystore
  • To get the debug certificate fingerprint:
keytool -list -v \
-alias androiddebugkey -keystore ~/.android/debug.keystore

Note: หากใช้ Debug Certificate ใส่รหัส Keystore คือ android

ซึ่งในบทความนี้จะขอใช้เป็น Debug Certificate Fingerprint ก่อนละกันคับ

ของจริงยาวกว่านี้ ผมขอปิดบางส่วนไว้นะคับ

ให้เรา Copy SHA1 มา และไปที่ Firebase Console และไปที่ Project Setting และเลื่อนลงไปล่างสุดจะเจอกับ App ของเราให้เลือก Android App ของเราและกด Add fingerprint

จากนั้นวาง SHA1ที่เรา Copy มาเมื่อกี้นี้ลงในกล่องและกด Save และจะเห็น Fingerprint ที่เราใส่ไปแสดงอยู่ ถือว่าเพิ่มสำเร็จ และจากนั้น Download google-services.json ลงมาและไปวางแทนที่อันเก่า เพราะเรามีการแก้ไขข้อมูล

เอาไปว่างไว้ใน Folder android > app แทนอันเก่า

เสร็จสิ้นการตั้งค่าใน Android (นี่เพิ่งเริ่มต้น ของจริงต่อจากนี้ 55555)

มาเริ่มเขียนเชื่อมต่อกับ Sign-in with Google กัน

อันดับแรก Import Package ก่อน

import 'package:google_sign_in/google_sign_in.dart';

ต่อมาประกาศตัวแปรให้กับ GoogleSignIn ของเรากันก่อนเลยโดยผมจะเก็บไว้ในตัวแปรชื่อ googleSignIn

final GoogleSignIn googleSignIn = GoogleSignIn();

และเราสามารถสั่ง Sign-in with Google ด้วยคำสั่งนี้ googleSignIn.signIn() ซึ่งจะเก็บไว้ในตัวแปรชื่อ googleUser

final GoogleSignInAccount googleUser = await googleSignIn.signIn();

เราจะได้ค่า GoogleSignInAccount กลับมา ต่อจากนั้นก็สั่ง Authentication ด้วยคำสั่งนี้ โดยการนำค่า GoogleSignInAccount จากขั้นที่แล้ว มา .authentication โดยเก็บค่าการ Authentication ไว้ในตัวแปรชื่อ googleAuth

final GoogleSignInAuthentication googleAuth = await googleUser.authentication;

จากนั้นเราจะไปเอา Credential มาเพื่อส่งต่อให้ Firebase Authentication โดยเราจะใช้คำสั่งว่า GoogleAuthProvider.getCredential(idToken: "", accessToken: "") ซึ่งการเอา Credential นั้นต้องใช้ idToken และ accessToken โดยเราสามารถหาได้จาก GoogleSignInAuthentication จากขั้นตอนที่แล้วนั้นเอง

final AuthCredential credential = GoogleAuthProvider.getCredential(idToken: googleAuth.idToken, accessToken: googleAuth.accessToken);

และขั้นตอนสุดท้ายก็คือการส่ง Credential ไป Sign-in ที่ Firebase Authentication

AuthResult authResult = await _auth.signInWithCredential(credential);

ซึ่งผมจะรวมทั้งหมดนั้นไว้ใน Function ชื่อ signInWithGoogle() ตามนี้เลยครับ

อย่างสุดท้าย ก็คือการ Sign-Out เมื่อ User สั่ง Sign-Out เป็นอันจบพิธีคับผม โดยจะแยกเป็น 2 ระบบ นั้นก็คือ Firebase Authentication และ Google โดยจะใช้คำสั่งตามนี้เลยครับ

  • Firebase Authentication (_auth เป็นตัวแปรที่เราสร้างขึ้นมาเพื่อ Reference ไปถึง FirebaseAuth.instance)
_auth.signOut()
  • Google (googleSignIn เป็นตัวแปรที่เราสร้างขึ้นมาเพื่อ Reference ไปถึง GoogleSignIn())
googleSignIn.signOut()

และนี่ก็เป็นตัวอย่างจากการ Sign-in With Google ใน Project นี้ครับ

-> Sign-In with Apple

สำหรับ IOS Developer นั้น Sign-In with Apple กลายมาเป็นสิ่งที่จำเลย ขาดไม่ได้เพราะอย่างที่ผมบอกไปว่าหาก App ของคุณมีการใช้ Third-Party Sign-In เมื่อไหร แล้วคุณไม่มี Sign-in With Apple. Apple จะน้อยใจและไม่ให้แอพของคุณไปต่อ 55555 แต่ก็มีข้อยกเว้นอยู่นะๆ ลองอ่านดูในหัวข้อ 4.8 ของ AppStore Review GuideLine

แต่ก็ไม่เป็นไร เพราะเราสามารถ Implement การ Sign-in With Apple ใน Flutter ได้แล้วนั้นเอง มาเริ่มกันเลย

อันดับแรก ไป Enable Sign-in Provider ที่ Firebase Console ก่อน

เละเหมือนเดิม อย่าลืมกด Save อิอิ

ต่อไปมา Enable Sign-in with Apple กัน โดยที่ เราจะทำในฝั่งของ IOS เท่านั้น Android ยังไม่ Support (อาจจะแปลกๆหน่อยถ้าบอกว่า Android Support Sign in with Apple 55555) เปิด File Runner.xcworkspace ใน Folder ios ขึ้นมา และไปที่ Runner > Signing & Capabilities และกด “+ Capability”

จากนั้นจะมีหน้าต่างนึงติดขึ้นมา ให้เราเลื่อนหาคำว่า “Sign in With Apple” และ Dubble Click เพื่อเพิ่ม Capabilities ให้กับ App ของเรา

จากนั้นกลับมาที่ Flutter Project ก็คงต้องพูดถึงเรื่องปุ่มก่อน เนื่องจากทาง Apple เค้าได้กำหนด รูปแบบของปุ่ม ขนาด สีบลาๆ.. ที่จะให้ User กดเพื่อ Sign in with Apple ไว้ด้วย (ถ้าไม่เป็นไปตาม Guideline App ของคุณก็จะไม่ได้ไปต่อเช่นกัน 55555) ลองอ่านดูในนี้ได้เลยคับ

ซึ่งใน Dependency ที่เราลงไปตอนต้น ก็ Provide Widget ที่เป็นปุ่มให้เราเรียบร้อยแล้ว (WOW ผมปลื้มสิ่งนี้ 5555) โดยให้เรา import package นี้เข้ามา

import 'package:apple_sign_in/apple_sign_in_button.dart';

ซึ่งเราสามารถเรียกใช้ได้โดย Widget ชื่อ AppleSignInButton

AppleSignInButton(     onPressed: () {},     cornerRadius: 30,     style: ButtonStyle.white,     type: ButtonType.signIn,)

เราสามารถตั้งได้ 4 Properties ตามนี้

  • onPressed เหมือนกับปุ่มทั้วไป เป็น Handler เมื่อมีการกดปุ่ม
  • cornerRadius ตั้งรัศมีความโค้งของขอบปุ่มได้
  • style ตั้งรูปแบบของปุ่มได้โดยจะมีอยู่ 3 แบบที่เราตั้งได้คือ

** ButtonStyle.black — ปุ่มสีดำ

** ButtonStyle.white — ปุ่มสีขาว

** ButtonStyle.whiteOutline — ปุ่มสีขาวแบบมีขอบ

  • type ตั้งรูปแบบคำที่แสดงบนปุ่มได้ โดยมีให้เราเลือก 2 แบบ คือ

** ButtonType.continueButton — เป็นคำว่า “Continue with Apple”

** ButtonType.signIn และ ButtonType.defaultButton — เหมือนกัน คือ “Sign in with Apple”

จบเรื่องปุ่ม มาเริ่ม Sign-in with Apple กันเลย

ก่อนอื่น Import package ของ Sign-in With Apple ก่อนเลย

import 'package:apple_sign_in/apple_sign_in.dart';

ซึ่งก่อนที่จะเริ่มการ Sign-in เราต้องตรวจสอบก่อนว่า Sign-in With Apple สามารถใช้งานบน Device นี้รึเปล่าด้วยคำสั่ง AppleSitnIn.isAvailable() ซึ่งจะ Return ค่า Future<bool> ออกมา ก็จัดการ Implement ด้วย If else เลย

if (await AppleSignIn.isAvailable()){   ....
} else {
....
}

จากนั้นมาเริ่มสั่ง Sign-in with Apple กัน เราจะใช้คำสั่งว่า AppleSignIn.performRequest([]) โดยใน [] จะรับค่า AuthorizationRequest ซึ่งปัจจุบันยังรองรับแค่ AppleIdRequest ซึ่งใน AppleIdRequest จะรับค่า Scope ที่เราต้องการขอเข้าถึงข้อมูลของ User ซึ่งจะรองรับอยู่ 2 อย่างคือ email และ fullName สุดท้ายแล้วคำสั่งนี้จะ Return ค้า Future<AuthorizationResult> กลับมาให้เรา ก็จัดการ Implement ให้เรียบร้อย ก็จะได้คำสั่งหน้าตาประมาณนี้

final AuthorizationResult result = await AppleSignIn.performRequests([
AppleIdRequest(requestedScopes: [Scope.email,Scope.fullName])
]);

หลังจากนั้น เราต้องทำ Handler หลังจากที่ User Login ซึ่งจะถูก Return กลับมาอยู่ใน AuthorizationResult ซึ่งผมประกาศเป็น result ไปเมื่อขั้นตอนที่แล้ว โดยจะใช้คำสั่งในการเอาค่า Status ออกมาว่า result.status ซึ่ง Status ทั้งหมดมี 3 Status โดยจะถูก Return ออกมาในรูปแบบของ AuthorizationStatus

  • AuthorizationStatus.authorized — เมื่อ User Sign-in ให้ Permission ในการเข้าถึงข้อมูลและ Sign-in สำเร็จ
  • AuthorizationStatus.error — เมื่อเกิดข้อผิดพลาดขึ้นระหว่างการ Sign-in
  • AuthorizationStatus.cancelled — เมื่อ User กดปุ่ม Cancel เพื่อยกเลิกการ Sign-in

โดยเราสามารถใช้ switch case ในการ handle Status ได้

switch (result.status) {    case AuthorizationStatus.authorized:       ....       break;    case AuthorizationStatus.error:       ....           break;    case AuthorizationStatus.cancelled:       ....       break;}

ต่อไปเป็นการส่ง Credential ไป Sign-in ที่ Firebase Authentication โดยทำคล้ายๆกับของ Google คือเราต้องเอา Credential จาก Apple ไปสร้าง Credential ในรูปแบบของ Firebase Credential โดยจะใช้คำสั่งตามนี้ครับ

OAUTHProvider(providerId: "apple.com").getCredential(idToken: "", accessToken: "")

และสิ่งที่เราต้อง Pass ให้กับคำสั่งนี้คือ idToken และ accessToken ซึ่งค่าทั้งหมดอยู่ในตัวแปร result หมดแล้ว แต่ค่ามันเป็น Uint8List ดังนั้นจัดการเปลี่ยนให้มันเป็น String ด้วยคำสั่ง String.fromCharCode()

AuthCredential credential = OAuthProvider(providerId: "apple.com").getCredential(  idToken: String.fromCharCodes(result.credential.identityToken),  accessToken: String.fromCharCodes(result.credential
.authorizationCode),
);

จากนั้นส่ง Credential นี้ไปให้ Firebase Authentication เพื่อ Sign-in ด้วยคำสั่งนี้ ซึ่งจะ Return ค่า Future<AuthResult> กลับมาให้เรา

Reminder: _auth คือตัวแปรที่ประกาศไปถึง FirebaseAuth.instance

_auth.signInWithCredential(credential)

และผมจะจับทุกอย่างมาไว้ใน Function เดียวกันเพื่อให้เรียกใช้งานง่ายๆ หน้าตาเป็นแบบนี้เลยคับ

ก่อนจบ Sign-in With Apple มาบอกอีก 1 อย่างที่ควรจะทำคือ อย่างที่ผมบอกไปว่า Sign-in With Apple นั้นAvailable บน iOS เท่านั้น ดังนั้น เราควรที่จะเช็คมันก่อน ว่าเป็น iOS หรือ Android ก่อนที่จะลงปุ่มไปใน Widget หรือดักการทำงานของปุ่มด้วยคำสั่งนี้

import 'dart:io' show Platform;if (Platform.isIOS) {
....
} else {
....
}

-> Sign-in with LINE Login v2.1 (Custom Authentication)

มาถึงหัวข้อพิเศษของบทความนี้แล้ว นั้นก็คือการทำ Custom Authentication ด้วย LINE Login v2.1

หากใครยังไม่เคยใช้ LINE Login สามารถอานบทความเกี่ยวกับการติดตั้ง LINE Login บท Flutter ได้ในบทความนี้เลยครับ

ในบทความนี้จะวาปมาตอนที่เราติดตั้ง LINE Login SDK บน Flutter Project เรียบร้อยแล้ว ก่อนอื่นต้องอธิบายหลักการของการทำ Custom Authentication กันก่อน

ถ้าพูดง่ายๆแล้ว Firebase Authentication นั้นมีการ Sign-in อยู่หลักๆ 3 แบบด้วยกันคือ

  1. Sign in ด้วย Email, Phone ของ Firebase Auth เอง
  2. Sign in with Credential ที่ได้จาก 3th Party Sign-in (ตาม Provider ที่ Firebase Auth กำหนด)
  3. Sign in with Token เป็นการ Sign in ด้วย Firebase Custom Token 1 ชุดที่ถูกสร้างขึ้นมาโดยจะ Sign in และ อ้างอิงค์ไปถึงตัว User ที่ต้องการ Sign in ได้เลย โดย 3th Party Sign in ที่ไม่ได้อยู่ใน Provider ที่ Firebase รองรับ ก็จะต้องมาใช้วิธีนี้ LINE Login ก็ต้องใช้วิธีนี้เช่นกัน

มาดูภาพรวมที่เราต้องทำเพื่อใช้ Custom Authentication โดยใช้ LINE Login กันดีกว่า

สามารถอ่านบทความเพิ่มเติมเกี่ยวกับ LINE Login x Firebase Authentication ได้ในบทความของพี่ตี๋ Jirawatee ได้เลยครับบ

ถ้าสั่งเกตุจากแผนภาพ จะมีการใช้ Cloud Functions เข้ามาเกี่ยวด้วย เพราะ การสร้าง Custom Token นั้นต้องใช้ผู้ที่มีสิทธิในการเข้าถึง Firebase Authentication อยู่แล้ว เพื่อไปเอาข้อมูล User มาสร้าง Token นั้นก็หมายถึง Firebase Admin นั้นเอง โดยเราจะต้องส่ง Credential ต่างๆดังนี้ไปให้กับ Firebase Admin

  • userId
  • displayName
  • profileImage
  • email
  • channelId
  • accessToken

เพื่อให้ Firebase Admin สามารถระบุได้ว่าใครที่กำลัง Sign-in เข้ามา และสร้าง Custom Token ได้ถูก แต่ถ้ายังไม่เคยมีบัญชีใน Firebase Auth เลย Firebase Admin จะสร้าง Account ให้และทำการสร้าง Custom Token กลับมาให้เรา ทฤษฏีมาเยอะแล้ว มาปฏิบัติกันบ้างดีกว่า 55555

อันดับแรก เราต้องสั่ง Sign-in With LINE ก่อน ด้วยคำสั่งนี้ครับ ซึ่งผมจะเก็บ Login Result ไว้ในตัวแปรชื่อ lineLoginResult และสำคัญมากๆที่จะต้องใส่ scopes email ลงไป เพราะเราต้องการ email ของ User ด้วย เพื่อใช้ ยืนยันใน Firebase Authentication (อย่าลืมไป Apply Email Address Permissionใน Developer Console ด้วยนะ)

final lineLoginResult = await LineSDK.instance.login(scopes: ["profile", "email", "openid"]);

หลังจากที่ User Sign-in เสร็จแล้ว เราจะได้ค่า Login Result มา ซึ่งจะประกอบไปด้วยข้อมูลหลายอย่างที่เราต้องเอาออกมา ตามที่ผมเขียนไว้ด้านบน มาเริ่มดึงออกมาทีละตัวกันเลย (มี เซอร์ไพรส์ เล็กน้อยถึงปานกลาง รอติดตามครับ 55555)

accessToken

String accessToken = lineLoginResult.accessToken.data["access_token"];

channelId

อันนี้เราต้องเอามาจาก Developer Console หรือ ที่เราใช้ Init LINE SDK ครับ

โอเคมาถึงตรงนี้แล้ว ยังเหลือข้อมูลอีก 4 ตัว ซึ่งก็คือ displayName, profielImage, userId, email โดย 3 อันแรกจะสามารถดึงมาได้จาก Login Result แต่ email ยังงัยเราก็ต้องทำการ Decode ค่า id_token ที่เป็น JWT เพื่อเอามาอยู่ดีครับ ซึ่งข้างในก็มีค่าพวกนี้อยู่เหมือนกัน ซึ่งนี่คือเซอร์ไพรส์ที่ผมจะบอก คือบน IOS และ Android นั้น ค่า id_token ที่ได้รับมาจาก Login Result จะมีโครงสร้างต่างกัน

นี่คือค่าที่ได้จาก Android:

{"amr":["pwd"],"audience":"165405905","email":"me@sirateek.dev","expiresAt":"Apr 11, 2020 12:39:51 AM","issuedAt":"Apr 10, 2020 11:39:51 PM","issuer":"https://access.line.me","name":"Siratee K.","nonce":"cWt8BnNzd9LvpT","picture":"https://profile.line-scdn.net/0hMYCpE3LDEnZ-CTqB4wZtIUJMHBsJJxQ-Bj0NEVgAT0FbMVZ0RGcPQ1NZREIGb","rawString":"eyJraWQiOiJhMmE0NTlhZWM1YjY1ZmE0ZThhZGQ1Yzc2OTdjNzliZTQ0NWFlMzEyYmJjZDZlZWY4ZmUwOWI1YmI4MjZjZjNkIiwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJodHRwczovL2FjY2Vzcy5saW5lLm1lIiwic3ViIjoiVTFlZmJjNzk3YzcxNzRkZDYzNmMwNDdmNWNhOGViYTQyIiwiYXVkIjoiMTY1NDA1OTAyNSIsImV4cCI6MTU4NjU0MDM5MSwiaWF0IjoxNTg2NTM2NzkxLCJub25jZSI6ImNXdDhCbk56ZDlMdnBUNVAiLCJhbXIiOlsicHdkIl0sIm5hbWUiOiJTaXJhdGVlIEsuIiwicGljdHVyZSI6Imh0dHBzOi8vcHJvZmlsZS5saW5lLXNjZG4ubmV0LzBoTVlDcEUzTERFblotQ1RxQjR3WnRJVUpNSEJzSkp4US1CajBORVZnQVQwRmJNVlowUkdjUFExTlpSRUlHYmxVblJXaFpGZzlkVEVWVyIsImVtYWlsIjoic2lyYXRlZWtAZ21haWwuY29tIn0.pxpJ7xjGcc_DrVFXVy-mi_07geavkas"

นี่คือค่าที่ได้จาก IOS:

eyJraWQiOiIxZjA2N2VlYzU5OWI3NGJmNGUyOGMyNDNjN2ZiYTY0NjNhMDM1OTMzNTUzZTMxZjdhMzg4ZTE0ZDQ0ZmE0OGUzIiwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJodHRwczovL2FjY2Vzcy5saW5lLm1lIiwic3ViIjoiVTFlZmJjNzk3YzcxNzRkZDYzNmMwNDdmNWNhOGViYTQyIiwiYXVkIjoiMTY1NDA1OTAyNSIsImV4cCI6MTU4NjU0MDY3OCwiaWFZSI6Imh0dHBzOi8vcHJvZmlsZS5saW5lLXNjZG4ubmV0LzBoTVlDcEUzTERFblotQ1RxQjR3WnRJVUpNSEJzSkp4US1CajBORVZnQVQwRmJNVlowUkdjUFExTlpSRUlHYmxVblJXaFpGZzlkVEVWVyIsImVtYWlsIjoic2lyYXRlZWtAZ21haWwuY29tIn0.nUne3bLjuBMii_DjPVMT9TAgEqfIyqm2h1P5OVbJHVc5ihVlU2S88viLomT

คำสั่งเดียวกัน เรียกข้อมูลตำแหน่งเดียวกัน แต่ได้ข้อมูลไม่เหมือนกัน เพราะ ฝั่งของ Android LINE SDK Ver 5.x นั้นเค้า Decode ตัว id_token ที่เป็น JWT มาให้เราแล้วเพื่อให้เราเรียกง่ายๆนั้นเอง เลยออกมามีลักษณะเป็น JSON Obj แต่ IOS นั้น LINE SDK ไม่ได้ Decode ออกมาให้ ทำให้ ได้มาแค่ JWT ซึ่งจุดนี้ต้องแยกโค๊ดที่จะไปดึงข้อมูลระหว่าง 2 Platform ครับ ซึ่งผมจะใช้ข้อมูลจากใน id_token ทั้งหมดเลยเพื่อจะให้โค๊ดสั้นที่สุด โดยก่อนอื่น ต้องพูดถึงการ Decode ตัว JWT ก่อน เราจะใช้ Function นี้ สามารถ Copy ไปวางเลยครับ

Map<String, dynamic> tryParseJwt(String token) {    if (token == null) return null;    final parts = token.split('.');    if (parts.length != 3) {       return null;    }    final payload = parts[1];    var normalized = base64Url.normalize(payload);    var resp = utf8.decode(base64Url.decode(normalized));    final payloadMap = json.decode(resp);    if (payloadMap is! Map<String, dynamic>) {       return null;    }    return payloadMap;}

และเราจะเรียกใช้ Function นี้โดยส่งข้อมูลเข้าไปให้มัน ซึ่ง iOS และ Android จะได้ข้อมูลจากตำแหน่งที่ต่างกันตามโค๊ดนี้เลยครับ

import 'dart:io' show Platform;
import 'dart:convert';
var jwtDecode;
if (Platform.isIOS) { jwtDecode = tryParseJwt(
lineLoginResult.accessToken.data["id_token"]);
}if (Platform.isAndroid) { jwtDecode = tryParseJwt(
jsonDecode(lineLoginResult.accessToken.data["id_token"])["rawString"]);
}

จากโค๊ดด้านบน สังเกตุว่า ของ Android จะต้องใช้ jsonDecode() เข้ามาช่วยด้วย เนื่องจากค่าที่เราได้มาจากฝั่ง Android นั้นเป็น JSON Obj. ซึ่ง Dart ต้อง Decode ข้อมูลออกมาเป็น Map ก่อน แล้วจึงสามารถดึงข้อมูลในนั้นได้ ต่อไปก็มาดึงข้อมูลต่างๆออกมาจาก JWT กัน ข้อมูลทั้งหมดผมประกาศไว้ในตัวแปรชื่อ jwtDecode เรียบร้อยแล้ว ซึ่งสิ่งที่จะได้กลับมาก็คือ Map ดังนั้นเราจะสามารถดึงข้อมูลได้ดังนี้

String displayName = jwtDecode["name"];String userId = jwtDecode["sub"];String profileImage = jwtDecode["picture"] ??"https://firebasestorage.googleapis.com/v0/b/flutter-firebase-d754b.appspot.com/o/avatar-human-male-profile-user-icon-518358.png?alt=media&token=44f84be1-ae20-4b47-aed3-0e67cda10897";String email = jwtDecode["email"];String channelId = "Enter your channelId";

โดยข้อมูลที่มีโอกาสที่จะ Null ได้มี 2 ตัวคือ profileImage และ email ดังนั้นก็จัดการดักหรือ Handle ส่วนนี้ด้วยนะครับ จากนั้น ก็เป็นการส่ง Request ข้อมูลทั้งหมดไปให้กับ Cloud Function กันแล้ว ส่วนนี้ผมจะขอไม่อธิบายนะครับ สามารถไปอ่านได้ในบทความของพี่ตี๋ด้านบนได้เลยครับ ซึ่งเดียวผมจะแปะโค๊ดของ Function แบบเสร็จแล้วไว้ให้แทนครับ

ซึ่งโค๊ดที่ผมพัฒนามาจะแตกต่างจากของพี่ตี๋นิดนึงตรงที่ผมจะให้มันตรวจสอบจาก Email แทน UserId เพราะหากเราตรวจสอบจาก UserId แล้วจะทำให้ Email มีสิทธิที่จะซ้ำกันได้ หากเราใช้ Sign-in Provider อื่นๆร่วมด้วย ทำให้เกิด Error ขึ้นนั้นเอง

กลับมาที่ Flutter เราจะใช้ Dependency ชื่อ http ในการทำ HTTP Request จัดการลงให้เรียบร้อย และ Import เข้ามาเลย

import 'package:http/http.dart' as http;

จากนั้นทำการ Pack ข้อมูลทุกๆอย่างให้อยู่ใน Map

Map<String, dynamic> reqBody = {   "accessToken": accessToken,   "displayName": displayName,   "userId": userId,   "profileImage": profileImage,   "channelId": channelId,   "email": email};

และก็ทำการยิง Request โลด!

var firebaseToken = (await http.post("Your Cloud Function URL",
body: reqBody)
).body;

สิ่งที่เราจะได้มาจาก Cloud Function คือ CustomToken นั้นเอง ซึ่งจะอยู่ในตัวแปร firebaseToken ให้เราทำการสั่ง sign in ด้วย Firebase Authentication ได้เลยด้วยคำสั่งนี้

AuthResult result = await _auth.signInWithCustomToken(token: firebaseToken);

เป็นอันจบพิธีครับผม เย้~~~~

โค๊ดฉบับเต็มๆของการ Sign-in with LINE จะมีหน้าตาแบบนี้เลยครับ

และนี้ก็เป็นลิงค์ Git Repo. ที่ผมพัฒนามาในบทความนี้ทั้งหมดครับ

ก่อนจบบทความนี้ ก็มาพูดกันถึงการดึงข้อมูลหลังจากที่เรา Sign-in User เสร็จแล้วกันหน่อย โดยหลังจากที่เรา Sign-in เสร็จแล้วเราจะได้ค่า AuthResult มา ซึ่งหากสำเร็จ เราจะสามารถ .user ซึ่งเราจะได้ค่า Firebase User ออกมา จากตรงนี้เราจะสามารถเอาข้อมูลเกี่ยวกับ User และ แก้ไขข้อมูลของ User ได้ เช่นการดึง UserId เราสามารถใช้คำสั่งนี้ดึงออกมาได้

String uid = result.user.uid;

{Outro}

จบไปแล้วสำหรับบทความแนะนำการใช้งาน Firebase Authentication ใน Flutter ถ้าชอบบทความนี้อย่าลืมกดปุ่มปรบมือ กดติดตาม เพื่อเป็นกำลังใจให้ผมด้วยนะครับ

COVID-19 ทำพิษกับหลายคนหลายธุรกิจมากช่วงนี้ ก็อย่าลืมดูแลสุขภาพ ล้างมือให้สะอาดบ่อยๆด้วยนะครับ ขอให้ผ่านจุดนี้ไปให้ได้ครับ 😁😁😁😁

ก่อนจบบทความ ก็มี Poll สำรวจความคิดเห็นกันเล็กน้อยมาให้ทุกๆคนช่วยกันทำ เพื่อผมจะได้นำคำติชมจากทุกคนมาปรับปรุงและพัฒนาการเขียนบทความในอนาคตครับ ขอบคุณครับ

ขอบคุณที่เข้ามาอ่านบทความนี้ และพบกันในบทความถัดไปครับ,

ต้นน้ำ.

--

--

Siratee K.
Firebase Thailand

A wild Software Engineer. 🐤 Fascinated with Low-Level Computing & Computer Networking & Computer Architecture 🧑‍💻