AppAuth for Androidを用いてHUAWEI IDログインを実装する

# 前置き
HUAWEI IDログインを実装するのに、よく使われているのは[HMS Account Kit](https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides/android-introduction-0000001098842325)を導入する方法です。しかし、この方法ではAndroid端末にHMS Coreがインストールされていることが前提条件になっています。幸い、HUAWEI IDログインではOAuth 2.0が提供されているので、[AppAuth for Android](https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides/app-auth-access-huaweiid-android-0000001050434521)を利用すれば、HMS CoreがインストールされていないAndroid端末でもHUAWEI IDログインが使えます。

# AppAuth for Androidを利用するメリット

1. HMS Coreがいりません。
2. 独自実装より楽です。

# AppAuth for Androidの導入方法

[AppAuth for Androidの使い方](https://qiita.com/Rei_2020/items/db5431e5f8ed0bbb073f)で基本的な使い方を解説しました。ここではHUAWEI IDログインに特化した導入方法を説明します。

## AppGallery Connectでプロジェクトを作成

[“HMS Account Kit実装入門”の“前準備”](https://qiita.com/Rei_2020/items/81001ffc6299e56401c8#%E5%89%8D%E6%BA%96%E5%82%99)に書かれた手順に従って、プロジェクトを作成してください。ただし、agconnect-services.jsonをappフォルダに配置する必要はありません。

### OAuth 2.0 client ID

クライアントIDは[Project setting]→[General Information]→[App information]→[OAuth 2.0 client ID]→[Client ID]にあります。

## ライブラリの導入

appモジュールのbuild.gradleに
1. <b>AppAuth for Android</b>（[net.openid:appauth](https://search.maven.org/artifact/net.openid/appauth)）
2. <b>JWT Decoding library for Android</b>（[com.auth0.android:jwtdecode](https://mvnrepository.com/artifact/com.auth0.android/jwtdecode)）
を追加します。

```gradle:build.gradle
// AppAuth SDK：最新バージョンはhttps://search.maven.org/artifact/net.openid/appauth
implementation 'net.openid:appauth:0.11.1'
// For decode ID Token：最新バージョンはhttps://mvnrepository.com/artifact/com.auth0.android/jwtdecode
implementation 'com.auth0.android:jwtdecode:2.0.0'
```

## AndroidManifest.xmlの編集

AndroidManifest.xmlに次のようにリダイレクト用のactivityを追加します。

```xml:AndroidManifest.xml
...
<activity
        android:name="net.openid.appauth.RedirectUriReceiverActivity"
        tools:node="replace">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:scheme="com.huawei.apps.{OAuth 2.0 client ID}"/>
    </intent-filter>
</activity>
...
```

## AuthStateとAuthorizationServiceConfigurationを用意

空のAuthStateとAuthorizationServiceConfigurationを生成します。

```kotlin
private val appAuthState: AuthState = AuthState.jsonDeserialize("{}")
private val config = AuthorizationServiceConfiguration(
    Uri.parse("https://oauth-login.cloud.huawei.com/oauth2/v3/authorize"),
    Uri.parse("https://oauth-login.cloud.huawei.com/oauth2/v3/token")
)
```

## サインイン

### サインイン画面を表示

1. AuthorizationRequestを生成します。
2. スコープを設定します。
3. AuthorizationServiceのgetAuthorizationRequestIntentで、サインインのIntentを作ります。
4. startActivityForResultでサインインのIntentを起動します。

```kotlin
fun signIn() {
    val authorizationRequest = AuthorizationRequest
        .Builder(
            config,
            {OAuth 2.0 client ID},
            ResponseTypeValues.CODE,
            Uri.parse(com.huawei.apps.{OAuth 2.0 client ID}:/oauth2redirect)
        )
        .setScope({例：openid email profile})
        .build()
    val intent = AuthorizationService(context).getAuthorizationRequestIntent(authorizationRequest)
    startActivityForResult(intent, requestCode)
}
```

### サインインの認証結果を取得

1. onActivityResultでサインインの認証結果を受け取ります。
2. AuthStateを更新します。

<b>認証成功の条件</b>
AuthorizationResponseが空ではなく、かつAuthorizationExceptionが空である

<b>認証失敗の条件</b>
AuthorizationResponseが空、またはAuthorizationExceptionが空ではない

```kotlin
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    val authorizationResponse = AuthorizationResponse.fromIntent(data)
    val exception = AuthorizationException.fromIntent(data)
    appAuthState.update(authorizationResponse, exception)

    if (exception != null || authorizationResponse == null) {
        // 認証失敗
    } else {
        // 認証成功
    }
}
```

### 各トークンを取得

1. AuthorizationResponseを使ってトークンを要求します。
2. (1)のレスポンスを使ってAuthStateを更新します。
3. AuthStateのaccessTokenにアクセストークンが入っています。
4. AuthStateのidTokenにIDトークンが入っています。

```kotlin
AuthorizationService(context)
    .performTokenRequest(authorizationResponse.createTokenExchangeRequest()) { tokenResponse, authorizationException ->
        appAuthState.update(tokenResponse, authorizationException)
        tokenResponse?.let {
            // アクセストークン＝appAuthState.accessToken
            // IDトークン＝appAuthState.idToken
        }
    }
```

### IDトークンをユーザー情報に変換

JWTでIDトークンを解凍したら、次のユーザー情報が取得できます。

```kotlin
val jwt = JWT(idToken)
// Open ID
val openId = jwt.subject
// 姓名
val name = jwt.claims["display_name"]?.asString()
// プロフィール画像のURL
val pictureUrl = jwt.claims["picture"]?.asString()
// メールアドレス
val email = jwt.claims["email"]?.asString()
// メールアドレスは認証済みかどうか
val emailVerified = jwt.claims["email_verified"]?.asBoolean()
```

# GitHub
[AppAuthDemo(Huawei Google)](https://github.com/Rei2020GitHub/MyPublicProject/tree/master/AppAuthDemo(Huawei%20Google))

# 参考
[AppAuth for Android](https://github.com/openid/AppAuth-Android)
[AppAuth for Androidを用いてソーシャルログインを実装する](https://qiita.com/Rei_2020/items/db5431e5f8ed0bbb073f)
[Accessing Account Kit Using AppAuth](https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides/app-auth-access-huaweiid-android-0000001050434521)
[HMS Account Kit](https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides/android-introduction-0000001098842325)