日々是好日

プログラミングについてのあれこれ、ムダ知識など

AndroidでMiAuth実装してみた

リポジトリ

AndroidでMiAuthを実装しました。

手っ取り早くソース寄こせって人はこちらから。 developmentブランチです。

あと、かなりクラスを行ったり来たりするので、コード表示しながら読んでいただければ幸いです。

github.com

Misskey Hub

解説では明示されていませんが、カスタムスキーマは現在受け付けておらず、httpsに統一しているそうです(しゅいろママが言ってた)。

misskey-hub.net

トークン取得フロー

MiAuthは、次のようなイメージでアクセストークンを取得します。 OAuth2は未実装なので、詳細については言及しません。

さらに詳細を文字起こしにするとこんな感じ。

(一応MiAuth想定のフロー)
認証開始
 -> ViewModel#startAuth
 -> AuthUseCase#startAuth
 -> ユーザによりアプリ連携が許可される
 -> Activity#onNewIntentでコールバックURLのキャッチ
 -> RegisterAppCallback#onRegistered で受信する
 -> Interactor#requestToken
 -> トークンが返ってくる(成功の場合)
 -> ViewModel で実装し Interactor に渡されている AuthResultCallback を叩いてトークンを ViewModel に渡す
 -> ViewModel から PreferenceUseCase 等を通じてトークンを保存する

インターフェース定義

AuthActivityAuthActivityViewModelは認証フローの詳細を知らず、認証サーバとのやりとりはすべてAuthUseCaseを実装した各種インタラクタが実行します。 ViewModel はほとんどの場合、Activityからのイベントをインタラクタに通知するだけです。

また、MVVM を採用しているので、ViewModel は各種 UseCase を集約しています。

AuthUseCase

MiAuth以外にも対応できるようにsealed interfaceで定義しました。 sealed interfaceとすることで、AuthUseCaseを次のようにobject句で実装しようとしてもエラーになります。

また、どんな認証フローでもstartAuthを叩くことでフローを開始するように縛ります。

fun foo() {
  val XxxAuthInteractor = object : AuthUseCase {
    ...
  }
}
//-> This type is sealed, so it can be inherited by only its own nested classes or objects

AuthUseCaseは次のとおり定義します。

sealed interface AuthUseCase {
  fun startAuth(context: Context, ticket: AuthTicket, resultCallback: AuthResultCallback): RegisterAppCallback
  fun onDismiss()
  interface MiAuthUseCase: AuthUseCase
  interface OAuth2UseCase: AuthUseCase
}

//ユーザがアプリ連携を許可した際に必要なコールバック
//フローにActivity#onNewIntentが絡むため、何かしらの形でインタラクタに通知する仕組みが必要
interface RegisterAppCallback {
  suspend fun onRegistered(callbackIntent: Intent)
  suspend fun onFailed(err: Exception)
}

//トークンをリクエストした際のサーバのレスポンス
//取得に成功した場合、tokenが格納される
sealed interface AuthResultCallback {
  fun onAuthFinish(ticket: AuthTicket, token: Token)
  fun onFailed(err: Exception)
  interface MiAuthResultCallback: AuthResultCallback
  interface OAuth2ResultCallback: AuthResultCallback
}

AuthTicketは各種パーミッションやホスト情報等をまとめた単なるデータクラスです。 こちらもsealedで定義します。

github.com

MiAuthInteractor と OAuth2Interactor

AuthUseCaseを実装した具象クラス。 このように認証方式ごとにAuthUseCaseを実装したクラスを作ることで、startAuthを叩くだけで異なる認証フローを起動できるようにしています(たぶん)。

また、RegisnterAppCallbackInterector#startAuth にて、AuthResultCallback は ViewModel にて実装しています。

MiAuthInteractor github.com

AuthActivityViewModel github.com

雑なまとめ

  • 一部甘い部分(クラス変数にしている部分とか)があるけどそこはスルーで。
  • 別にMiAuthでトークン取得するテストなだけならここまでやる必要はない。。。