HMSアプリにGoogleログイン、Facebookログイン、Twitterログインを実装する方法

# ソーシャルログインについて
ソーシャルログインとはユーザーが自分の所有しているSNSアカウントを使って、ログインや新規登録をする機能です。メールアドレスや電話番号などの入力がなくなるので、その利便さがユーザーエクスペリエンスの向上につながります。そのため、最近ソーシャルログインを実装したアプリが増えています。

国内でよく使われているソーシャルログインはLINE、Google、Facebook、Twitterなどあります。

<img width="300" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704548/b9f2e868-98dc-3a21-0dff-07480e857500.png">

# HUAWEI Auth Service
HUAWEI Auth Serviceはファーウェイが提供しているソーシャルログイン機能です。このソーシャルログイン機能はAndoidでもiOSでも実装可能です。また、GMSのAndroid端末でもHMSのAndroid端末でも使用可能です。HUAWEI Auth ServiceがサポートしているSNSアカウントの認証トークンさえあればログインできます。

現在HUAWEI Auth ServiceがサポートしているSNSアカウントは次の表に示します。

|SNSアカウント|Android|iOS|Web|
|:---:|:---:|:---:|:---:|
|HUAWEI ID|〇|||
|HUAWEI GameCenter account|〇|||
|WeChat account|〇|〇|〇|
|QQ account|〇|〇|〇|
|Weibo account|〇|〇||
|Apple ID||〇||
|Google account|〇|〇||
|Google Play Games account|〇|||
|Facebook account|〇|〇||
|Twitter account|〇|〇||

# HUAWEI Auth Serviceの実装方法
ソーシャルログインの流れは次のようになります。

1. SNSアカウントの認証サーバーから認証トークンを取得する
2. 取得した認証トークンをHUAWEI Auth Serviceの認証サーバーに渡し、ログインする

ここで注意しなければならないのは、HUAWEI Auth Serviceは（２）の機能のみを提供しているということです。（１）の機能は含まれていません。

本稿は（１）と（２）の実装方法をすべて紹介します。

## 開発準備

1. AppGallery Connectに入って、[My project]をクリックする
2. 左側のメニューから[Auth Service]をクリックし、Auth Serviceを開く
3. [Enable now]をクリックし、Auth Serviceを有効にする

![image.jpg](https://communityfile-drcn.op.hicloud.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20201224110405.30261860920662460021043457721107:50511224081726:2800:66C28928943941CC39AD11BDDC1521609CD5087496896711DD0B52FA7092950A.png)

[Huawei account]、[Facebook]、[Twitter]、[Google]を有効にする。SNS開発者アカウントのApp IDやApp Keyもここに設定します。

![image.jpg](https://communityfile-drcn.op.hicloud.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20201224110405.69098140571036299448959362773954:50511224081726:2800:2581B39E44E7642D3D9CD552977FB8B4E72778AD4D5A4BA26B359FABD35B987E.png)

![image.jpg](https://communityfile-drcn.op.hicloud.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20201224110405.73234474524547374487543646677053:50511224081726:2800:4E0311F081E3E8C5734EC370314856FE8D2ACCF5F426FF030EB2AF290E2F5735.png)

build.gradleにライブラリを追加します。

```gradle:build.gradle
implementation 'com.huawei.agconnect:agconnect-core:1.4.2.301'
implementation 'com.huawei.agconnect:agconnect-auth:1.4.2.301'
```

## HUAWEI IDのトークン取得の実装方法

```gradle:build.gradle
implementation 'com.huawei.hms:hwid:5.0.5.301'
```

HuaweiIdAuthServiceを生成し、Activityを起動します。

```kotlin:MainActivity.kt
val huaweiIdAuthParamsHelper = HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM).apply {
    setScopeList(arrayListOf(Scope(CommonConstant.SCOPE.ACCOUNT_BASEPROFILE)))
}
val huaweiIdAuthParams: HuaweiIdAuthParams = huaweiIdAuthParamsHelper.setAccessToken().createParams()
val huaweiIdAuthService: HuaweiIdAuthService = HuaweiIdAuthManager.getService(activity, huaweiIdAuthParams)
startActivityForResult(huaweiIdAuthService.signInIntent, REQUEST_CODE_SIGN_IN_HUAWEI_ID)
```

HUAWEI IDのアクセストークンはonActivityResultに返ってきます。

```kotlin:MainActivity.kt
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    ...

    when (requestCode) {
        REQUEST_CODE_SIGN_IN_HUAWEI_ID -> {
            val authHuaweiIdTask = HuaweiIdAuthManager.parseAuthResultFromIntent(data)
            if (authHuaweiIdTask.isSuccessful) {
                // HUAWEI IDのアクセストークン = authHuaweiIdTask.result.accessToken
                val credential = HwIdAuthProvider.credentialWithToken(authHuaweiIdTask.result.accessToken)
            }
        }
    }
}
```

## Facebookのトークン取得の実装方法

Facebookログインのクイックスタートにしたがって、リソースやソースコードを自分のプロジェクトにコピペします。

![image.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704548/db9a7815-db6e-128f-849c-b6f35e89f73a.jpeg)


```gradle:build.gradle
implementation 'com.facebook.android:facebook-login:8.1.0'
```

```xml:strings.xml
<string name="facebook_app_id">xxxxxxxxxxxxxx</string>
<string name="fb_login_protocol_scheme">xxxxxxxxxxxxxx</string>
```

```xml:main_fragment.xml
<com.facebook.login.widget.LoginButton
    android:id="@+id/button_sign_in_facebook"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    />
```

```kotlin:MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    ...
    FacebookSdk.sdkInitialize(applicationContext)
}
```

```kotlin:MainFragment.kt
private val facebookCallbackManager: CallbackManager = CallbackManager.Factory.create()

override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
): View? {
    ...

    binding.buttonSignInFacebook.registerCallback(
            facebookCallbackManager,
            object : FacebookCallback<LoginResult?> {
                override fun onSuccess(result: LoginResult?) {
                    result?.let { result ->
                        // アクセストークン = result.accessToken.token
                        val credential = FacebookAuthProvider.credentialWithToken(result.accessToken.token)
                    }
                }

                override fun onCancel() {
                }

                override fun onError(error: FacebookException?) {
                }
            })

    ...
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    ...

    facebookCallbackManager.onActivityResult(requestCode, resultCode, data)
}
```

## Twitterのトークン取得の実装方法

TwitterのアプリID、アプリキー、アプリキーシークレットはTwitter Developer Portalから見られます。

![image.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704548/e0c8c445-2b25-0584-9a6a-651877873d7c.jpeg)

![image.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704548/115fdd11-b2c7-04d7-1271-75d22b5c9407.jpeg)


```gradle:build:gradle
implementation 'com.twitter.sdk.android:twitter:3.3.0'
```

```xml:strings.xml
<string name="twitter_app_id">xxxxxx</string>
<string name="twitter_app_key">xxxxxxxxxxxxxxxxxxxxxxxx</string>
<string name="twitter_app_key_secret">xxxxxxxxxxxxxxxxxxxxx</string>
```

```xml:main_fragment.xml
<com.twitter.sdk.android.core.identity.TwitterLoginButton
    android:id="@+id/button_sign_in_twitter"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    />
```

```kotlin:MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    ...

    val twitterAuthConfig = TwitterAuthConfig(
            getString(R.string.twitter_app_key),
            getString(R.string.twitter_app_key_secret)
    )
    val twitterConfig: TwitterConfig = TwitterConfig.Builder(applicationContext).twitterAuthConfig(
            twitterAuthConfig
    ).build()
    Twitter.initialize(twitterConfig)
}

// Twitterのログインロジックがフラグメントにある場合
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    ...

    supportFragmentManager.findFragmentByTag(MainFragment.TAG)?.let {
        it.onActivityResult(requestCode, resultCode, data)
    }
}
```

```kotlin:MainFragment.kt
override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
): View? {
    ...

    binding.buttonSignInTwitter.callback = object : com.twitter.sdk.android.core.Callback<TwitterSession>() {
        override fun success(result: Result<TwitterSession>?) {
            result?.let { result ->
                // アクセストークン = result.data.authToken.token
                // アクセストークンシークレット = result.data.authToken.secret
                val credential = TwitterAuthProvider.credentialWithToken(
                        result.data.authToken.token,
                        result.data.authToken.secret
                )
            }
        }

        override fun failure(exception: TwitterException?) {
        }
    }

    ...
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    ...

    binding.buttonSignInTwitter.onActivityResult(requestCode, resultCode, data)
}
```

## Googleのトークン取得の実装方法
### 認証情報の作成
Google Developer Consoleで認証情報を作成します。

ここで注意しなければならないことがあります。HMS端末にGMSが入っていないので、Googleが提供しているSDKが使えません。そのため、Google Developer Consoleで認証情報を作成するときに、ウェブアプリケーションを選ばなければなりません。

![image.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704548/fc2725a1-888d-ec27-7fec-710b8e1b54ba.jpeg)

作成したら、クライアントIDとクライアントシークレットが表示されます。

また、リダイレクトURI（URI）を設定しなければなりません。このURIは公開されている実在のページやAPIじゃなくてもよいです。httpまたはhttpsから始まるURIであればよいです。

![image.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704548/9c4163be-bf56-8130-b4a1-b5f6dd4630dd.jpeg)



GoogleのSDKを使わないので、build.gradleに追加するコードがありません。


### 認証コードの取得
https://accounts.google.com/o/oauth2/auth でGoogleアカウントの認証コードを取得します。

https://accounts.google.com/o/oauth2/auth はGETメソッドのAPIです。まずURLを作ります。

```kotlin:GoogleAuth.kt
companion object {
    private const val AUTH_LINK = "https://accounts.google.com/o/oauth2/auth"
    private const val CLIENT_ID = "your client id"
    private const val SCOPE = "https://www.googleapis.com/auth/userinfo.profile"
    const val REDIRECT_URI = "https://your.redirecturi.com/auth"

    }

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
}

fun createAuthLink(): String {
    val map: Map<String, String> = hashMapOf(
            "client_id" to CLIENT_ID,
            "scope" to SCOPE,
            "response_type" to "code",
            "redirect_uri" to REDIRECT_URI
    )

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

URLを内部WebViewで開きます。

```kotlin:MainFragment.kt
val googleAuth = GoogleAuth()
val link = googleAuth.createAuthLink()
val intent = Intent(context, WebViewActivity::class.java).apply {
    putExtra("link", link)
}

startActivity(intent)
```

内部WebViewで開くので、WebViewのActivityを作ります。

```xml:AndroidManifest.xml
<activity android:name=".WebViewActivity"></activity>
```

WebViewを作るときのポイントは次の通りです。

1. JavaScriptを有効にする
2. ユーザーエージェントを通常のブラウザと同じものにする
3. shouldOverrideUrlLoadingを必ずオーバーライドする
4. shouldOverrideUrlLoadingのリンクがGoogle Developer Consoleに設定したリダイレクトURIと同じであれば、リダイレクトをせずにMainActivityに転送する
5. Google Developer Consoleに設定したリダイレクトURIと違えば、WebView内で開く

```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.settings.userAgentString = "Mozilla/5.0 (Linux; Android 10; Pixel 3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.62 Mobile Safari/537.36"

            webView.webViewClient = object : WebViewClient() {
                override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
                    request?.url?.let { url ->
                        if (url.toString().contains(GoogleAuth.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)
    }
}
```

MainActivityで認証コードを取得します。

```kotlin:MainActivity.kt
val code = intent?.data?.getQueryParameter("code")
```

### 認証コードをトークンに変換
https://oauth2.googleapis.com/token で認証コードをトークンに変換します。

```kotlin:GoogleAuth.kt
companion object {

    private const val TOKEN_LINK = "https://oauth2.googleapis.com/token"
    private const val CLIENT_ID = "Your client id"
    private const val CLIENT_SECRET = "Your client secret"
    private const val SCOPE = "https://www.googleapis.com/auth/userinfo.profile"
    const val REDIRECT_URI = "https://your.redirecturi.com/auth"
    }

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

    val postRequest: StringRequest = object : StringRequest(
            Method.POST,
            TOKEN_LINK,
            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["code"] = code
            params["client_id"] = CLIENT_ID
            params["client_secret"] = CLIENT_SECRET
            params["redirect_uri"] = REDIRECT_URI
            params["grant_type"] = "authorization_code"
            return params
        }
    }

    queue.add(postRequest)
}
```

```kotlin:MainFragment.kt
private fun signInGoogleCode(code: String) {
    val context: Context = context ?: return
    val googleAuth = GoogleAuth()
    googleAuth.getToken(
        context,
        code,
        object : Response.Listener<String>{
            override fun onResponse(response: String?) {
                response?.let { response ->
                    val jsonObject = JSONObject(response)
                    if (jsonObject.has("id_token")) {
                        // トークンはhttps://oauth2.googleapis.com/tokenのレスポンスのid_token
                        val idToken = jsonObject.getString("id_token")
                        val credential = GoogleAuthProvider.credentialWithToken(idToken)
                    }
                }
            }
        },
        object : Response.ErrorListener{
            override fun onErrorResponse(error: VolleyError?) {
            }
        }
    )
}
```

## 各トークンによるHuawei Auth Serviceの認証の実装方法

```kotlin:MainFragment.kt
private fun signIn(credential: AGConnectAuthCredential) {
    AGConnectAuth.getInstance().signIn(credential).addOnSuccessListener { result ->
        // ユーザーオブジェクト
        val user = result.user

        // ユーザーのユニークなID
        val uid = user.uid

        // ユーザーのメールアドレス
        val email = user.email

        // ユーザーの電話番号
        val phone = user.phone

        // ユーザーの画像
        val photo = user.photoUrl

        // ユーザー名
        val name = user.displayName
    }.addOnFailureListener {
    }
}
```

# 一言
HMS端末のアプリではGoogleログインが使えないと勘違いするかもしれませんが、ちゃんと使えます！


# GitHub
[https://github.com/Rei2020GitHub/MyPublicProject/tree/master/AuthDemo](https://github.com/Rei2020GitHub/MyPublicProject/tree/master/AuthDemo)

# 参考
* HMS：[https://developer.huawei.com/consumer/jp/](https://developer.huawei.com/consumer/jp/)
* HMS Auth Service：[https://developer.huawei.com/consumer/jp/doc/development/AppGallery-connect-Guides/agc-auth-introduction-0000001053732605](https://developer.huawei.com/consumer/jp/doc/development/AppGallery-connect-Guides/agc-auth-introduction-0000001053732605)
* Huawei Developers：[https://forums.developer.huawei.com/forumPortal/en/home](https://forums.developer.huawei.com/forumPortal/en/home)
* Facebook Huawei Developersグループ：[https://www.facebook.com/Huaweidevs/](https://www.facebook.com/Huaweidevs/)