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

# 前置き
Android端末において、Googleログインの実装方法として、よく使われているのはGoogle Play開発者サービスを利用するやり方です。しかし、Google Play開発者サービスが無効になった場合、Googleログインができなくなります。また、国や地域、メーカーによって、そもそもAndroid端末にGoogle Play開発者サービスが入っていないことがあります。

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

1. GoogleのOAuthでは、webviewをサポートしなくなりました。AppAuth for AndroidはChrome Custom Tabをサポートしているので、GoogleのOAuthを利用できます。（https://developers-jp.googleblog.com/2016/09/modernizing-oauth-interactions-in-native-apps.html）
2. Google Play開発者サービスがいりません。
3. 独自実装より楽です。

# AppAuth for Androidの導入方法

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

## Google Cloud Platformの登録

1. Google Cloud Platformでプロジェクトを作成します。
2. [APIとサービス]→[OAuth同意画面]に入り、OAuth 同意画面の設定をします。スコープもここで設定します。
3. [APIとサービス]→[認証情報]に入り、[認証情報を作成]→[OAuthクライアントID]をクリックし、[アプリケーションの種類]で“Android”を選び、その他の必要な情報を設定します。
4. 設定が終わったら、<b>クライアントID</b>が表示されます。この<b>クライアントID</b>はソースコードで使われます。

## ライブラリの導入

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="{アプリのパッケージ名}"/>
    </intent-filter>
</activity>
...
```

## AuthStateとAuthorizationServiceConfigurationを用意

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

```kotlin
private val appAuthState: AuthState = AuthState.jsonDeserialize("{}")
private val config = AuthorizationServiceConfiguration(
    Uri.parse("https://accounts.google.com/o/oauth2/v2/auth"),
    Uri.parse("https://www.googleapis.com/oauth2/v4/token")
)
```

## サインイン

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

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

```kotlin
fun signIn() {
    val authorizationRequest = AuthorizationRequest
        .Builder(
            config,
            {クライアントID},
            ResponseTypeValues.CODE,
            Uri.parse({アプリのパッケージ名}:/{適当なパス})
        )
        .setScope({Google Cloud Platformで設定したスコープ。例：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トークンをユーザー情報に変換

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

```kotlin
val jwt = JWT(idToken)
// Open ID
val openId = jwt.subject
// 姓名
val name = jwt.claims["name"]?.asString()
// 姓
val familyName = jwt.claims["family_name"]?.asString()
// 名
val givenName = jwt.claims["given_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)