これは iOS Advent Calendar 2017 の17日目の記事です。
はじめに
はじめまして、Flatt という会社でエンジニアをしている machio(まちお) と申します。iOSはまだ書き始めて半年もたたないひよっこですが、これくらいのプレッシャーでもないと勉強しないだろうということで今回エントリーしました。
現在 PinQul というライブショッピングのアプリを運営しています。作り始めた時はiOS初学だった僕ですが、最高のメンターさんと今をときめくFirebaseの力のおかげでこのアプリを2ヶ月で仕上げることができました。
タイトルの通りPinQulでは "Firebase Authentication の idToken をサーバーの認証に使い自サービスのUserと紐づけて使用" しています。
このケースに関する記事は僕の知る限りこの世にあまり存在しないので、Firebaseへの恩返しの意味もこめて、この認証のiOS側の実装の方法とそこで苦労したことについて書こうと思います(Firebaseのcalendarでやれ)。
Firebase is 何?
Firebase は俗にいう mBaaS(Mobile Backend as a Service)の1つです。2014年にGoogleに買収されて一躍有名になりました。詳細は以下に素晴らしいQiitaの記事を添付して割愛させていただきます。
正直 使い勝手が良すぎる & なんでもできてしまう のでもう本格的にサーバーレスで生きていけるなと思ってしまうほど素敵なサービスです(唯一欠点を挙げるとすれば日本語の公式リファレンスが古いのでサンプルコードがたまにアレです)。
Firebase Authenticationとは
公式のリファレンス によると
Firebase Authentication には、バックエンド サービス、使いやすい SDK、アプリでのユーザー認証に使用できる UI ライブラリが用意されています。Firebase Authentication では、パスワード、電話番号、一般的なフェデレーション ID プロバイダ(Google、Facebook、Twitter)などを使用した認証を行うことができます。
です。いろんなログイン方法が簡単に実装できるだけではなく、それらでログインしたUserをFirebaseのsdkを通すだけで一元に管理できるのでとても素敵です。
そしてその方法で認証したUserには idToken なるものが発行されます。これは他のFirebaseのサービスへのアクセスに使用するというのがスタンダードな使い方だと思うのですが、公式のsdkを使ってあれこれするだけで自サービスのサーバーサイドの認証にも使えます。
実際の実装
手順としては
- Firebaseを通してログイン完了
- クライアントでidTokenを取得
- それに該当するJWT(JSON Web Token)をリクエストにのせて送信
- サーバー側でそれを検証することで認証
という感じです。1の実装に関しては 公式リファレンスに詳しく書いてある & すでに世にたくさん記事があるので割愛したいと思います。
1さえ超えてしまえばclientでやらなければいけないことはとても単純です。idTokenはとても簡単に取得することができます。
import Firebase Auth.auth().currentUser?.getIDToken { idToken, error in if let error = error { // エラーハンドリング return } if let idToken = idToken { // idTokenが使える } }
そしてリクエストのheaderにtokenを配置します。PinQulではHTTPクライアントに @ishkawa さんの APIKit を使わせていただいているので、以下のような感じになります。
import APIKit struct HogeRequest: APIKit.Request { var idToken: String … var headerFields: [String: String] { return ["Authorization": "Bearer \(idToken)"] } … }
Auth.auth().currentUser?.getIDToken に渡すコールバックの中でhogehogeしたり、RxつかったりでRequestにidTokenをつけることができれば、あとは送るだけです。とんでもなく簡単です。
あとはこのidTokenを検証した後decodeしてuuidなどの情報を得ます。
単に実装するだけならばこれだけなのですが、PinQulでは一点めんどくさいところがあったので最後にそれに触れて終わりたいと思います。
ログインが必要な機能とそうでない機能がある。。。
これは少しめんどくさかったです。世の中のライブストリーミングのサービスのほとんどがライブを見るだけであればログインをスキップできるようになっています。PinQulも御多分に洩れずそういった実装にしているので認証(idToken)が必要な場合とそうでない場合があります。
なのでリクエストを飛ばす時にそのリクエストに認証が必要かを判断する必要があります。なのでその処理に特化したprotocolを用意して、各Requestにそれを継承させ、認証がいるかいらないかの判断をジェネリクスを使って行わせることにしました。
import APIKit protocol PinQulRequestProtocol: APIKit.Request { … var needLogin: Bool { get set } var idToken: String? { get set } … } struct HogeRequest: PinQulRequestProtocol { var needLogin var idToken: String? … var headerFields: [String: String] { if needLogin, let idToken = idToken { return ["Authorization": "Bearer \(idToken)"] } } … }
実際に認証が必要かを判断してリクエストを送る処理は APIKit の Session にextensionを貼って実装しています。
import APIKit extension Session { class func sendPinQulRequest<T: PinQulRequestProtocol>(pinQulRequest: T, handler: @escaping (Result<T.Response, SessionTaskError>) -> Void) { if pinQulRequest.needLogin { Auth.auth().currentUser?.getIDToken { idToken, error in if let error = error { // エラーハンドリング return } var pinQulRequest = pinQulRequest pinQulRequest.idToken = idToken self.send(pinQulRequest, handler: handler) } } else { self.send(pinQulRequest, handler: handler) } } }
これでiOS側の実装は完璧です!
あとはサーバー側でこれを検証して、正常に認証できたらdecodeしてデータを取り出しましょう。 公式のリファレンスに詳細に書いてあるので問題ないと思います。
おわりに
Xcodeも今ではとても使いやすくなっていますし、Firebaseなんていうとても素敵なサービスが現れてアプリの開発のハードルがとても下がり、僕のような初学者でもゴリゴリアプリが作れるようになりました。
この記事で利益を被るのは本当にごくわずかな人だと思いますが、最後まで拙い文章をよんでいただきありがとうございます。まだまだ本当に力不足なのでマサカリ大歓迎です。バンバンお願いします!
PinQulは以下のURLからインストールできます。ぜひ手にとって使って見てください!