🐄

iOSウィジェットから認証済みAPIリクエストを送信する方法

2021/11/11に公開

iOSのウィジェット・エクステンションからアプリケーション本体でKeychainに保存したAPIトークンを読み出し、認証ありのAPIリクエストを送信する方法についてまとめてみました。

U-motionウィジェット

異なるアプリケーションの間でKeychainを共有するKeychain Sharing

iOSウィジェットで認証ありのAPIリクエストをしたいのですが、ウィジェットではログイン処理などのUIを実装できません。ログイン処理はアプリケーション本体で行い、何らかの方法でセッション・トークンなどの認証情報をウィジェットにシェアすることになります。今回はKeychainを使ってみました。

iOSではアプリケーション本体とウィジェット・エクステンションは別アプリのような扱いになっていて、そのままではウィジェットからアプリケーションが管理するKeychainにアクセスすることができません。Keychain Sharingを使ってアプリからKeychainを「共有」する必要があります。

https://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_apps

Keychain Sharingについてはこちらの記事を参考にしています。

https://evgenii.com/blog/sharing-keychain-in-ios/

アプリケーション本体のKeychain Sharingを有効化

Capabilitiesの設定

Xcodeでプロジェクトを選択したあとでアプリケーション本体のターゲットを選択、「Capabilities」のタブで「Keychain Sharing」を追加します。

Keychain Groupの欄には今回シェアするKeychainのグループ名を追加します。「WidgetSharingGroup」とします。

Entitlementsの設定

Entitlementsを開きKeychain Access Groupのキーを追加、値は「$(AppIdentifierPrefix)WidgetSharingGroup」にします。先程Capabilitiesで設定したグループ名の先頭にアプリケーションIDを差し込んだ文字列になります。

Keychain Access Groupは必ずApplication ID(Team ID)を含むようになっていて、これによってKeychainの公開範囲を同一の配布元のアプリケーションに限定します。

ウィジェット・エクステンションでKeychain Sharingを有効化

Widget側でもKeychain Sharingを有効にする必要があります。XcodeのターゲットでWidget Extensionを選択して、アプリケーション本体と同様にKeychain Sharingの設定をしていきます。Keychainのグループ名はアプリケーション本体と同じになります。

ウィジェットのコードから共有されたKeychainにアクセス

KeychainAccessを導入

ウィジェットからKeychainにアクセスするために、CocoaPodsの KeychainAccessを導入しました。

https://github.com/kishikawakatsumi/KeychainAccess

ウィジェットにCocoaPodsのパッケージを導入するためにPodfileに記述を追加します。

Podfile
target 'WidgetExtension' do
  use_frameworks!
  pod 'KeychainAccess', '~> 4.2'  
end

getTimeline()でKeychainにアクセス

ウィジェットのコンテンツはgetTimeline()で生成します。ここでKeychainAccessを使います。引数「accessGroup」でアプリケーション本体から共有されたKeychainグループを指定します。

Widget.swift

struct SessionToken: Codable {
  var sessionToken: String
  var expiredAt: Int
  var refreshToken: String
}

func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {...途中省略...let keychain = Keychain(service: "app", accessGroup: "(Entitilementsに追加した、AppIDを含むグループ名)")
  let result = try! keychain.getData("(取得したいデータのキー)")
  
  guard let sessionTokenJSON = result else { return }
  let decoder = JSONDecoder()
  let sessionToken = try? decoder.decode(SessionToken.self, from: sessionTokenJSON)
}

まとめ

ウィジェットのタイムライン生成時にKeychainからセッション・トークンを取り出すことができるようになりました。このトークンを使ってAPIサーバにアクセスして表示データを所得できます。

ログイン画面を実装できないiOSウィジェットで認証付きのAPIリクエストができるようになりました。この記事ではセッション・トークンのリフレッシュなどの処理は省いて記述していますが、ウィジェット内でセッション・トークンを更新した場合は、新しいトークン本体アプリケーションのKeyChainに書き戻す処理などが必要になると思います。

はじめてこのブログを読んだ方へ: U-motionとは?

U-motionは牛の首につけたセンサーを使って活動内容を記録、AIの力で健康状態を解析して畜産農家さんをサポートするモニタリング・システムです。
U-motion開発部は、

  • センサーから集めたデータを処理するバックエンド・システム
  • データをグラフ化するReactベースのウェブ・アプリケーション
  • 牛の健康状態の変化をプッシュ通知のアラートでお知らせするReact Nativeベースのスマホ・アプリケーション

などを開発中。こちらのアカウントでは、開発時に得た知見を公開しています。

https://www.desamis.co.jp/product/

https://www.youtube.com/watch?v=6Bw1wXX0i3s

Discussion