Android端末におけるHUAWEI IDログイン機能の詳細実装手順７－GMS端末でログイン１

# REST APIによるログイン機能の実装方法
ログイン機能は３つの処理に分けられます。
１．ログイン画面を表示する処理
２．ログイン結果（認証コード）を受け取る処理
３．認証コードをIDトークンに交換する処理

### ログイン画面を表示する処理
まず、Webログイン画面のリンクを生成します。
リンク：https://oauth-login.cloud.huawei.com/oauth2/v3/authorize
パラメータ：

|パラメータ名|値|
|:---:|:---|
|client_id|AppGallery Connectのプロジェクト設定に表示されるApp IDです。<br>AGCUtils.getAppId()を使えば、agconnect-services.jsonの中の"app_id"の値を取得できます|
|scope|"openid+profile"|
|response_type|"code"|
|access_type|必須ではないが、"offline"を渡せばリフレッシュトークンも返ってきます|
|redirect_uri|AppGallery ConnectのAccount Kitに設定したリダイレクトURIです|

こちらのページも合わせてご参照ください：

https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides-V5/web-get-access-token-0000001050048946-V5


```kotlin:NonHmsHuaweiIdLogic.kt
companion object {
    private const val AUTH_LINK = "https://oauth-login.cloud.huawei.com/oauth2/v3/authorize"
    private const val SCOPE = "openid+profile"
    const val REDIRECT_URI = "http://www.hmsaccountkit.com"
}

override fun signIn(activity: Activity) {
    activity.startActivity(generateSignInIntent(activity.applicationContext))
}

private fun generateSignInIntent(context: Context): Intent {
    return Intent(context, WebViewActivity::class.java).apply {
        putExtra("link", createAuthLink(context))
    }
}

private fun mapToString(map: Map<String, String>): String {
    var query = ""
    var count = 0
    map.forEach { (key, value) ->
        if (count > 0) {
            query += "&"
        }
        query += "$key=$value"
        count++
    }

    return query
}

private fun createAuthLink(context: Context): String {
    val map: Map<String, String> = hashMapOf(
        "client_id" to AGCUtils.getAppId(context),
        "scope" to SCOPE,
        "response_type" to "code",
        "access_type" to "offline",
        "redirect_uri" to REDIRECT_URI
    )

    return AUTH_LINK + "?" + mapToString(map)
}

```

Webログイン画面のリンクをWebViewの中に表示します。

### ログイン結果（認証コード）を受け取る処理
ログイン結果はリダイレクトURIのパラメータとして返ってきます。そのため、次のようにWebViewActivityクラスを作ります。

まず、レイアウトはWebViewのみで大丈夫です。

```xml:web_view_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/main_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <WebView
            android:id="@+id/web_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>
```

次はWebViewActivityクラスです。
まず、webviewのJavaScriptを有効にします。
さらに、shouldOverrideUrlLoading()の中で、開くリンクがリダイレクトURIと一致しているかどうかをチェックし、一致していれば、それを開かずに、パラメータ（認証コード）と一緒にMainActivityに転送します。

```kotlin:WebViewActivity.kt
class WebViewActivity : AppCompatActivity() {

    private lateinit var binding: WebViewActivityBinding

    @SuppressLint("SetJavaScriptEnabled")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.inflate<WebViewActivityBinding>(
                layoutInflater,
                R.layout.web_view_activity,
                null,
                false
        )
        setContentView(binding.root)

        with(binding) {
            webView.settings.javaScriptEnabled = true
            webView.settings.domStorageEnabled = true

            webView.webViewClient = object : WebViewClient() {
                override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
                    request?.url?.let { url ->
                        if (url.toString().contains(NonHmsHuaweiIdLogic.REDIRECT_URI)) {
                            val intent = Intent(applicationContext, MainActivity::class.java).apply {
                                data = url
                                flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
                            }
                            startActivity(intent)
                            return true
                        }

                        view?.loadUrl(url.toString())
                    }

                    return false
                }
            }

            intent?.getStringExtra("link")?.let { url ->
                webView.loadUrl(url)
            }
        }

        window.setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
    }

}
```

認証コードはIntentのdataに格納されます。

```kotlin
intent.data?.getQueryParameter("code")
```

### 認証コードをIDトークンに交換する処理

認証コードはそのまま使えません。ユーザー情報が入っているIDトークンに交換する必要があります。

まずIntentから認証コードを取得します

```kotlin:MainActivity.kt
override fun onStart() {
    super.onStart()

    huaweiIdLogic.update(applicationContext, intent)
}
```

```kotlin:NonHmsHuaweiIdLogic.kt
override fun update(context: Context, intent: Intent) {
    intent.data?.getQueryParameter("code")?.let { code ->
        signInWithCode(context, code)
    }
}
```

次はAPIを呼び出し、認証コードをIDトークンに交換します。
API：https://oauth-login.cloud.huawei.com/oauth2/v3/token
メソッド：POST
パラメータ：

|パラメータ名|値|
|:---:|:---|
|grant_type|"authorization_code"|
|client_id|AppGallery Connectのプロジェクト設定に表示されるApp IDです。<br>AGCUtils.getAppId()を使えば、agconnect-services.jsonの中の"app_id"の値を取得できます|
|client_secret|AppGallery Connectのプロジェクト設定に表示されるApp Secretです。|
|code|認証コード|
|redirect_uri|AppGallery ConnectのAccount Kitに設定したリダイレクトURIです|

こちらのページも合わせてご参照ください：

https://developer.huawei.com/consumer/jp/doc/development/HMSCore-References-V5/account-obtain-token_hms_reference-0000001050048618-V5

```kotlin:NonHmsHuaweiIdLogic.kt
companion object {
    private const val GET_ID_TOKEN_URL = "https://oauth-login.cloud.huawei.com/oauth2/v3/token"
}

private fun signInWithCode(context: Context, code: String) {
    val context: Context = context ?: return
    getIdToken(
        context,
        code,
        object : Response.Listener<String> {
            override fun onResponse(response: String?) {
                response?.let { response ->
                    val jsonObject = JSONObject(response)
                    if (jsonObject.has("id_token")) {
                        // 権限を取り消すときにアクセストークンが必要
                        val accessToken = jsonObject.getString("access_token")

                        // IDトークンにユーザー情報が入っている
                        val idToken = jsonObject.getString("id_token")
                    }
                }
            }
        },
        object : Response.ErrorListener {
            override fun onErrorResponse(error: VolleyError?) {
                error?.printStackTrace()
            }
        }
    )
}

private fun getIdToken(
    context: Context,
    authorizationCode: String,
    listener: Response.Listener<String>,
    errorListener: Response.ErrorListener
) {
    val queue = Volley.newRequestQueue(context)

    val postRequest: StringRequest = object : StringRequest(
        Method.POST,
        GET_ID_TOKEN_URL,
        listener,
        errorListener
    ) {
        override fun getBodyContentType(): String {
            return "application/x-www-form-urlencoded"
        }

        override fun getParams(): Map<String, String> {
            val params: MutableMap<String, String> = HashMap()
            params["grant_type"] = "authorization_code"
            params["client_id"] = AGCUtils.getAppId(context) ?: ""
            params["client_secret"] = APP_SECRET
            params["code"] = authorizationCode
            params["redirect_uri"] = REDIRECT_URI
            return params
        }
    }

    queue.add(postRequest)
}
```

これでIDトークンが取得できます。
また、アクセストークンも同時に返ってきます。アクセストークンは権限取り消すときに必要です。

# シリーズ
- [Android端末におけるHUAWEI IDログイン機能の詳細実装手順１－ソリューションの概要](https://qiita.com/Rei_2020/items/e02814bd688a41ab8702)
- [Android端末におけるHUAWEI IDログイン機能の詳細実装手順２－実装準備](https://qiita.com/Rei_2020/items/2eb9dada38cdfc2729ef)
- [Android端末におけるHUAWEI IDログイン機能の詳細実装手順３－HMS判定](https://qiita.com/Rei_2020/items/285107103be525ce3f98)
- [Android端末におけるHUAWEI IDログイン機能の詳細実装手順４－HMS端末でログイン](https://qiita.com/Rei_2020/items/548781581caf3e2f4046)
- [Android端末におけるHUAWEI IDログイン機能の詳細実装手順５－HMS端末でログアウト](https://qiita.com/Rei_2020/items/cc214859b82f518a1cd7)
- [Android端末におけるHUAWEI IDログイン機能の詳細実装手順６－HMS端末で権限取り消し](https://qiita.com/Rei_2020/items/a04a5894d5e565db969d)
- [Android端末におけるHUAWEI IDログイン機能の詳細実装手順７－GMS端末でログイン１](https://qiita.com/Rei_2020/items/0a7172d22817a5ae110f)
- [Android端末におけるHUAWEI IDログイン機能の詳細実装手順８－GMS端末でログイン２](https://qiita.com/Rei_2020/items/c083a16ef016eecdd5ad)
- [Android端末におけるHUAWEI IDログイン機能の詳細実装手順９－GMS端末でログアウト](https://qiita.com/Rei_2020/items/c0c1f1a68bfd0216cdd3)
- [Android端末におけるHUAWEI IDログイン機能の詳細実装手順１０－GMS端末で権限取り消し](https://qiita.com/Rei_2020/items/2afd389ae123e0552cae)

# GitHub
* https://github.com/Rei2020GitHub/MyPublicProject/tree/master/AccountDemo(H%2BG)

# 参考
* HMS：https://developer.huawei.com/consumer/jp/
* HMS Account Kitの紹介：https://developer.huawei.com/consumer/jp/doc/overview/HMS
* HMS Account Kitのドキュメント：https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides-V5/introduction-0000001050048870-V5
* Huawei Developers：https://forums.developer.huawei.com/forumPortal/en/home
* Facebook Huawei Developersグループ：https://www.facebook.com/Huaweidevs/