Ngày nay, khi sử dụng một dịch vụ online online, chúng ta có xu hướng sử dụng một tài khoản liên kết (Google, Facebook, Twitter… tạm gọi là bên thứ 3) để đăng nhập vào dịch vụ đó thay vì cứ mỗi một dịch vụ, ta lại tạo một account/passord riêng.
Bạn là một develop phát triển một service, để cung cấp tính năng này cho người dùng, bạn sẽ phải mất rất nhiều effort để “tự” liên kết các bên thứ 3 vào trong app của mình.
Lúc này Firebase Authentication
(từ đây sẽ gọi tắt là Firebase) sẽ là một giải pháp!
Firebase
hỗ trợ hầu hết các nhà cung cấp liên kết login (ngoài Google, Facebook, còn cả Github, Yahoo nữa…). Việc chúng ta cần làm đó là tích hợp Firebase
, phần còn lại Firebase
sẽ lo tất
User ở mỗi quốc gia, lại chuộng các nhà cung cấp login khác nhau. Với Nhật – một Khách Hàng lớn của đa số anh em dev chúng ta, lại chuộng login với mạng xã hội LINE Login. Tuy nhiên hiện tại Firebase chưa hỗ trợ login với LINE anh em ạ.
Song ta vẫn có cách!
Bài viết này mình sẽ giới thiệu một cách để tích hợp LINE login với Firebase sử dụng Custom Auth!
Sơ đồ tổng quát
Luồng login sẽ gồm 3 bước:
Bước 1: Sử dụng LINE Login SDK để User login, và nhận LINE Access Token
trả về
Bước 2: Gửi LINE Access Token
lên server của bạn, xác thực LINE Access Token
với LINE server. Nếu token hợp lệ, tạo Firebase Custom Auth token
tương ứng với user đó rồi gửi ngược về thiết bị của user.
Bước 3: Sử dụng Firebase Custom Auth token
để login vào Firebase từ thiết bị của user.
Cùng xem ví dụ code nhá~
Bước 0. chuẩn bị
Đầu tiên, cần setup LINE Business account và Project trên Firebase.
- Để tạo LINE Business account, bạn xem tại đây
- Tiếp theo, cài library Firebase Authentication library vào app (iOS / Android)
- Để tạo
Custom Auth token
ta có thể dùng Server SDK hoặc thư viện JSON Web Token là đủ.
Bước 1. Login vào LINE
Các bạn có thể xem docs của LINE: (iOS / Android) để được hướng dẫn cách tích hợp LINE SDK và implement luồng login với LINE.
Sau khi user login thành công, ta sẽ lấy được LINE access token
iOS (Objective-C)
1 2 | NSString <span class="token operator">*</span>lineAccessToken <span class="token operator">=</span> self<span class="token punctuation">.</span>lineAdapter<span class="token punctuation">.</span>getLineApiClient<span class="token punctuation">.</span>accessToken<span class="token punctuation">;</span> |
Android
1 2 3 | LineAuthManager authManager = LineSdkContextManager.getSdkContext().getAuthManager(); final String accessToken = authManager.getAccessToken().accessToken; |
Sau đó, bạn có thể sử dụng cách thức bạn hay dùng để gửi access token này lên server của bạn để tiếp tục bước xác thực. Trong ví dụ này, mình sử dụng GTM HTTP Fetcher cho iOS và Volley cho Android.
iOS (Objective-C)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | NSURL <span class="token operator">*</span>url <span class="token operator">=</span> <span class="token punctuation">[</span>NSURL URLWithString<span class="token operator">:</span>@<span class="token string">"https:///verifyToken"</span><span class="token punctuation">]</span><span class="token punctuation">;</span> NSMutableURLRequest <span class="token operator">*</span>request <span class="token operator">=</span> <span class="token punctuation">[</span>NSMutableURLRequest requestWithURL<span class="token operator">:</span>url<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">[</span>request setHTTPMethod<span class="token operator">:</span>@<span class="token string">"POST"</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">[</span>request setValue<span class="token operator">:</span>@<span class="token string">"application/json"</span> forHTTPHeaderField<span class="token operator">:</span>@<span class="token string">"content-type"</span><span class="token punctuation">]</span><span class="token punctuation">;</span> NSDictionary <span class="token operator">*</span>token <span class="token operator">=</span> @<span class="token punctuation">{</span>@<span class="token string">"token"</span> <span class="token operator">:</span> lineAccessToken<span class="token punctuation">}</span><span class="token punctuation">;</span> NSError <span class="token operator">*</span>error<span class="token punctuation">;</span> NSData <span class="token operator">*</span>requestBody <span class="token operator">=</span> <span class="token punctuation">[</span>NSJSONSerialization dataWithJSONObject<span class="token operator">:</span>token options<span class="token operator">:</span>kNilOptions error<span class="token operator">:</span><span class="token operator">&</span>error<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">[</span>request setHTTPBody<span class="token operator">:</span>requestBody<span class="token punctuation">]</span><span class="token punctuation">;</span> GTMHTTPFetcher <span class="token operator">*</span>fetcher <span class="token operator">=</span> <span class="token punctuation">[</span>GTMHTTPFetcher fetcherWithRequest<span class="token operator">:</span>request<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">[</span>fetcher beginFetchWithCompletionHandler<span class="token operator">:</span><span class="token operator">^</span><span class="token punctuation">(</span>NSData <span class="token operator">*</span>data<span class="token punctuation">,</span> NSError <span class="token operator">*</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Extract Firebase Custom Auth token from response</span> <span class="token comment">// ・・・</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">;</span> |
Android
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <span class="token class-name">HashMap</span> validationObject <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation"><</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> validationObject<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"token"</span><span class="token punctuation">,</span> accessToken<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">Response</span><span class="token punctuation">.</span><span class="token class-name">Listener</span> responseListener <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Response</span><span class="token punctuation">.</span><span class="token class-name">Listener</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">onResponse</span><span class="token punctuation">(</span><span class="token class-name">JSONObject</span> response<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Extract Firebase Custom Auth token from response</span> <span class="token comment">// ・・・</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token class-name">JsonObjectRequest</span> fbTokenRequest <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">JsonObjectRequest</span><span class="token punctuation">(</span> <span class="token class-name">Request</span><span class="token punctuation">.</span><span class="token class-name">Method</span><span class="token punctuation">.</span>POST<span class="token punctuation">,</span> <span class="token string">"https:///verifyToken"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">JSONObject</span><span class="token punctuation">(</span>validationObject<span class="token punctuation">)</span><span class="token punctuation">,</span> responseListener<span class="token punctuation">,</span> errorListener<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">NetworkSingleton</span><span class="token punctuation">.</span><span class="token function">getInstance</span><span class="token punctuation">(</span>activity<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addToRequestQueue</span><span class="token punctuation">(</span>fbTokenRequest<span class="token punctuation">)</span><span class="token punctuation">;</span> |
Bước 2. Tạo Firebase Custom Auth Token tương ứng từ LINE Access Token
Server của bạn sẽ làm nhiệm vụ kiểm chứng tính hợp lệ của LINE access token
sử dụng LINE Social Rest API. Nếu token hợp lệ thì tạo Firebase Custom Auth token
cho user LINE đó.
Chú ý: Đừng quên kiểm chứng giá trị channelId nhận được từ LINE API, để chắc chắn là access token đó được phát hành cho app của bạn. Điều này giúp ngăn chặn spoof attack. Hacker có thể sử dụng lại access token của hắn mà dành cho app khác để thử login vào app của chúng ta.
Server (Node.js)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | app<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string">'/verifyToken'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>req<span class="token punctuation">.</span>body<span class="token punctuation">.</span>token<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">400</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token string">'Access Token not found'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> reqToken <span class="token operator">=</span> req<span class="token punctuation">.</span>body<span class="token punctuation">.</span>token<span class="token punctuation">;</span> <span class="token comment">// Send request to LINE server for access token verification</span> <span class="token keyword">const</span> options <span class="token operator">=</span> <span class="token punctuation">{</span> url<span class="token operator">:</span> <span class="token string">'https://api.line.me/v1/oauth/verify'</span><span class="token punctuation">,</span> headers<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token string">'Authorization'</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Bearer </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>reqToken<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token function">request</span><span class="token punctuation">(</span>options<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">error<span class="token punctuation">,</span> response<span class="token punctuation">,</span> body</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>error <span class="token operator">&&</span> response<span class="token punctuation">.</span>statusCode <span class="token operator">===</span> <span class="token number">200</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> lineObj <span class="token operator">=</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>body<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Don't forget to verify the token's channelId to prevent spoof attack</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">typeof</span> lineObj<span class="token punctuation">.</span>mid <span class="token operator">!==</span> <span class="token string">'undefined'</span><span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token punctuation">(</span>lineObj<span class="token punctuation">.</span>channelId <span class="token operator">===</span> myLINEChannelId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Access Token Validation succeed with LINE server</span> <span class="token comment">// Generate Firebase token and return to device</span> <span class="token keyword">const</span> firebaseToken <span class="token operator">=</span> <span class="token function">generateFirebaseToken</span><span class="token punctuation">(</span>lineObj<span class="token punctuation">.</span>mid<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Update Firebase user profile with LINE profile</span> <span class="token function">updateUserProfile</span><span class="token punctuation">(</span>reqToken<span class="token punctuation">,</span> firebaseToken<span class="token punctuation">,</span> lineObj<span class="token punctuation">.</span>mid<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> ret <span class="token operator">=</span> <span class="token punctuation">{</span> firebase_token<span class="token operator">:</span> firebaseToken <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>ret<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> ret <span class="token operator">=</span> <span class="token punctuation">{</span> error_message<span class="token operator">:</span> <span class="token string">'Authentication error: Cannot verify access token.'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">403</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>ret<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
Sau khi kiếm chứng LINE Access Token là hợp lệ, sử dụng Firebase Server SDK
để tạo ra Firebase Custom Auth token
tương ứng rồi trả về cho thiết bị của user.
Server (Node.js)
1 2 3 4 5 6 7 8 | <span class="token keyword">function</span> <span class="token function">generateFirebaseToken</span><span class="token punctuation">(</span><span class="token parameter">lineMid</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> firebaseUid <span class="token operator">=</span> <span class="token string">'line:'</span> <span class="token operator">+</span> lineMid<span class="token punctuation">;</span> <span class="token keyword">var</span> additionalClaims <span class="token operator">=</span> <span class="token punctuation">{</span> provider<span class="token operator">:</span> <span class="token string">'LINE'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> firebase<span class="token punctuation">.</span><span class="token function">auth</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">createCustomToken</span><span class="token punctuation">(</span>firebaseUid<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Login đến Firebase sử dụng Custom Auth token
Tại phía thiết bị của user, sau khi nhận được Firebase Custom Auth token, chỉ cần dùng nó để login user vào Firebase là DONE!
iOS (Objective-C)
1 2 3 4 5 6 | <span class="token punctuation">[</span><span class="token punctuation">[</span>FIRAuth auth<span class="token punctuation">]</span> signInWithCustomToken<span class="token operator">:</span>firebaseToken completion<span class="token operator">:</span><span class="token operator">^</span><span class="token punctuation">(</span>FIRUser <span class="token operator">*</span> _Nullable user<span class="token punctuation">,</span> NSError <span class="token operator">*</span> _Nullable error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Process sign in result</span> <span class="token comment">// ・・・</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">;</span> |
Android
1 2 3 4 5 6 7 | <span class="token class-name">FirebaseAuth</span><span class="token punctuation">.</span><span class="token function">getInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">signInWithCustomToken</span><span class="token punctuation">(</span>firebaseToken<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">addOnCompleteListener</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">OnCompleteListener</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Process sign in result</span> <span class="token comment">// ・・・</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
Bài viết gốc: https://firebase.googleblog.com/2016/11/authenticate-your-firebase-users-with-line-login.html