AppAuth for Androidを用いてソーシャルログインを実装する

# 前置き
通常、ソーシャルログインを実装するのに、そのソーシャルログインのSDKを導入することが一般的です。しかし、この方法ですと、SDKに依存してしまうため、対象プラットフォームのSDKがなければ、導入ができなくなります。たとえば、

- HMSのAndroid端末において、Google IDまたはApple IDでログインする場合
- HMS Coreが入っていないAndroid端末において、HUAWEI IDでログインする場合
- GMSのAndroid端末において、Apple IDでログインする場合

上記の状況ですと、SDK導入によるソーシャルログインの実装では対応が不可能です。

また、SDKはプラットフォームごとに異なるため、ソースコードの管理において手間が増えます。

そこで、解決策としてOauthが出てきます。Oauthを利用すれば、プラットフォームとソーシャルログインSDKに依存せずに、ソーシャルログインを実装できます。

Android端末限定であれば、AppAuth for AndroidというOauthに特化したAndroid専用のSDKあり、それを使えば、OAuth 2.0に対応したソーシャルログインを簡単に実装できます。

# AppAuth for Androidの導入方法

## 対応OS

- Android 6.0以上

## ライブラリの導入

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)）
を追加します。

AppAuth for Androidを使ってソーシャルログインをし、JWT Decoding library for Androidを使ってログイン後に返ってくるIDトークンを解凍し、ユーザー情報を取得します。

```gradle:build.gradle
// AppAuth SDK
implementation 'net.openid:appauth:{バージョン}'
// For decode ID Token
implementation 'com.auth0.android:jwtdecode:{バージョン}'
```

また、それぞれの最新バージョンはこちらでご確認ください。
[AppAuth for Androidの最新バージョン](https://search.maven.org/artifact/net.openid/appauth)
[JWT Decoding library for Androidの最新バージョン](https://mvnrepository.com/artifact/com.auth0.android/jwtdecode)

## 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="{your scheme}"
              android:host="{your host}"
              android:path="{your path}"/>
    </intent-filter>
</activity>
...
```

\<data>要素の中身は対象のソーシャルログインによって異なります。たとえば、

<b>Google ID</b>：

```xml
<data android:scheme="{アプリのパッケージ名}"/>
```

<b>HUAWEI ID</b>：

```xml
<data android:scheme="com.huawei.apps.{OAuth 2.0 client ID}"/>
```

<b>Apple ID</b>：

```xml
<data android:scheme="https"
      android:host="{Developerサイトへ事前に登録したReturnURLsのホスト部分}"
      android:path="{Developerサイトへ事前に登録したReturnURLsのパス部分}"/>
```

<b>LINE ID</b>：

```xml
<data android:scheme="https"
      android:host="{LINE Developersコンソールに登録したコールバックURLのホスト部分}"
      android:path="{LINE Developersコンソールに登録したコールバックURLのパス部分}"/>
```

<b>Facebook ID</b>：

```xml
<data android:scheme="https"
      android:host="{クライアントOAuth設定に登録した有効なOAuthリダイレクトURIのホスト部分}"
      android:path="{クライアントOAuth設定に登録した有効なOAuthリダイレクトURIのパス部分}"/>
```

## AuthStateとAuthorizationServiceConfigurationを用意

空のAuthStateを生成し、ソーシャルログインの認証エンドポイントとトークンエンドポイントを用いてAuthorizationServiceConfigurationを生成します。

```kotlin
private val appAuthState: AuthState = AuthState.jsonDeserialize("{}")
private val config = AuthorizationServiceConfiguration(
    Uri.parse({認証エンドポイント}),
    Uri.parse({トークンエンドポイント})
)
```

## サインイン

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

1. ソーシャルログインのクライアントIDとリダイレクトURIと、前のステップで生成したAuthorizationServiceConfigurationを用いて、AuthorizationRequestを生成します。
2. スコープを設定します。
3. AuthorizationServiceのgetAuthorizationRequestIntentで、サインインのIntentを作ります。
4. startActivityForResultでサインインのIntentを起動します。サインインの結果はonActivityResultで返ってくるので、startActivityではなく、startActivityForResultでIntentを起動しなければなりません。

```kotlin
fun signIn() {
    val authorizationRequest = AuthorizationRequest
        .Builder(
            config,
            {ソーシャルログインのクライアントID},
            ResponseTypeValues.CODE,
            Uri.parse({ソーシャルログインのクライアントのリダイレクトURI})
        )
        .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のrefreshTokenにリフレッシュトークンが入っています。
5. AuthStateのidTokenにIDトークンが入っています。

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

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

1. JWTでIDトークンを解凍します。
2. OpenID＝jwt.subject
3. メールアドレス＝jwt.claims["email"]?.asString()


```kotlin
val jwt = JWT(idToken)
val openId = jwt.subject
val email = jwt.claims["email"]?.asString()
```

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

# 参考
[AppAuth for Android](https://github.com/openid/AppAuth-Android)