Firebase Authenticationを使ってみる🔥

初めまして。普段様々な機会を頂いてAndroid開発をやっている大学院生です。
もう早いもので12月も半分以上が過ぎてしまいましたね。

2016年も様々なことがありましたが、2016/05/19(日本時間)にGoogle I/O 2016にて[Firebaseの新バージョン](https://developers-jp.googleblog.com/2016/05/firebase.html)が発表されました。おかげで、Firebaseを使ってできることが更に増え、GoogleのいくつかのサービスがFirebaseに統合されました。

そんな中で、今年Firebaseの一つの機能である認証機能を触る機会がありました。もちろん、公式ドキュメントがあるのですが、公式ドキュメントだけではつまづくポイントがいくつかありましたので(特にTwitterとの連携周りで)、この機会にまとめておこうと思います。

今回認証の連携に用いたプロバイダサービスは、Google、Facebook、Twitterの3つです。これらに加えて、emailとpasswordの入力による認証を実装しました。

![](https://github.com/ronnnnn/FirebaseLoginSample/blob/master/gif/demo.gif?raw=true)

それぞれのプロバイダごとに説明していこうと思います。

# まず初めに
各プロバイダとの連携を説明する前に、まずはFirebaseをアプリに追加しましょう。
こちらは説明すると言っても、公式ドキュメントをなぞるだけになってしまうので、[公式ドキュメント](https://firebase.google.com/docs/android/setup)をご覧ください。
いきなり手抜いてすみません。笑

# Googleログインを用いて認証する
Firebaseをアプリに追加したら、いよいよプロバイダを用いた認証の実装です！ワクワクですね！！

まず、[Firebase console](https://firebase.google.com/console/)でGoogleログインを有効にします。
そして、`build.gradle`に以下を追加します。

```
compile 'com.google.firebase:firebase-auth:9.6.1'
compile 'com.google.android.gms:play-services-auth:9.6.1'
```

`GoogleSignInOption`オブジェクトにサインインの設定を追加していきます。

```java
GoogleSignInOptions googleSignInOptions =
                new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                        .requestIdToken(getString(R.string.web_client_id))
                        .requestEmail()
                        .build();
```
`requestIdToken`メソッドを設定するのを忘れないでください！この時の引数の文字列は[こちらのページ](https://console.developers.google.com/apis/credentials?project=_)内のOAuth 2.0 クライアントIDを設定してください。二つクライアントIDがあると思いますが、**ウェブアプリケーション**タイプのクライアントIDを設定しましょう。

![](https://c8.staticflickr.com/1/754/31574631191_9854827aad_k.jpg)

`DEFAULT_SIGN_IN`オプションではログインするアカウントの`ID`と`basic profile`のみしか取得できません。適宜オプションを追加してください。今回はメールアドレスも取得したかったので、`requestEmail()`を設定しています。

次に、`GoogleApiClient`オブジェクトを生成します。

```java
googleApiClient = new GoogleApiClient.Builder(this)
                .enableAutoManage(this/* FragmentActivity*/, this/*OnConnectionFailedListener*/)
                .addApi(Auth.GOOGLE_SIGN_IN_API, googleSignInOptions)
                .build();
```
`enableAutoManage`メソッドの第一引数は`FragmentActivity`を継承したActivityを渡してください。`AppCompatActivity`は`FragmentActivity`のサブクラスですので、多くの場合は`AppCompatActivity`を渡すことになるかと思います。第二引数には、`OnConnectionFailedListener`を渡します。このリスナーは読んで字のごとくGoogleApiClientへの接続に失敗した時にコールバックされます。

Googleの`SignInButton`をレイアウトして、ログイン時の挙動を設定していきます。

```java
SignInButton googleSignInButton = (SignInButton) findViewById(R.id.google_sign_in_button);
        googleSignInButton.setSize(SignInButton.SIZE_STANDARD);
        googleSignInButton.setOnClickListener(this);
```
今回は`onClick`メソッド内から次の`signInWithGoogle()`を呼んでいます。

```java
private void signInWithGoogle() {
        Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient);
        startActivityForResult(signInIntent, REQUEST_CODE_GOOGLE_SIGN_IN);
    }
```
そして、`OnActivityResult`メソッド内でsign inの結果を受け取ります。

```java
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == REQUEST_CODE_GOOGLE_SIGN_IN) {
            GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
            handleSignInWithGoogleResult(result);
    }
}
```
`onActivityResult`の引数である`Intent`から`getSignInResultFromIntent`メソッドを使って`GoogleSignInResult`オブジェクトを取得します。このオブジェクトを利用して、認証をハンドリングしていきます。

```java
private void handleSignInWithGoogleResult(GoogleSignInResult result) {
    if (result.isSuccess()) {
        // succeed sign in
        GoogleSignInAccount account = result.getSignInAccount();
        firebaseAuthWithGoogle(account);
    } else {
        // failed sign in
        DialogManager.createDialog(MainActivity.this,
                CommonStatusCodes.getStatusCodeString(result.getStatus().getStatusCode()))
                .show();
    }
}

private void firebaseAuthWithGoogle(GoogleSignInAccount account) {
    AuthCredential credential = GoogleAuthProvider.getCredential(account.getIdToken(), null);
    firebaseAuth.signInWithCredential(credential)
            .addOnCompleteListener(this/*Activity*/, this/*OnCompleteListener*/);
}
```
Googleアカウントへのログインが成功したら、先ほど取得した`GoogleSignInResult`オブジェクトから`IdToken`を取得し、Firebaseの認証情報と交換し、Firebaseの認証情報を使用してFirebaseでの認証を行います。

Firebaseでの認証が完了したら(成功・失敗に関わらず)、`addOnCompleteListener`メソッドの第二引数に指定している`OnCompleteListener`の`onComplete`メソッドがコールバックされます。このメソッド内では、失敗した時の処理だけ書いておけばいいです。今回は、認証の流れの中で何かしらのエラーが発生した場合は、ダイアログを表示しています。

```java
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
    if (!task.isSuccessful()) {
        DialogManager.createDialog(MainActivity.this, task.getException())
                .show();
    }
}
```

では、成功した場合はどうするかというところですが、

```java
@Override
protected void onStart() {
    super.onStart();
    firebaseAuth.addAuthStateListener(this);
}

@Override
protected void onStop() {
    super.onStop();
    firebaseAuth.removeAuthStateListener(this);
}
```
このようにして、ActivityにimplementしたAuthStateListenerを設定しておきます。

```java
@Override
public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
    FirebaseUser user = firebaseAuth.getCurrentUser();
    if (user != null) {
        // User has already signed in
    } else {
        // User has already signed out
    }
}
```
先ほどのリスナーを設定しておくことで、`onAuthStateChanged`メソッドがコールバックされます。この中で、Firebaseでの認証が成功した時の処理を書いていきます。
ちなみに、このメソッドは以下のタイミングでUIスレッドで呼ばれます。

- リスナーがセットされた直後 (Right after the listener has been registered)
- ユーザがサインインした時 (When a user is signed in)
- ログイン中のユーザがサインアウトした時(When the current user is signed out)
- ログインユーザが変更された時 (When the current user changes)
- ログイン中のユーザのtokenが変わった時 (When there is a change in the current user's token)

以上が、Googleログインを用いたFirebase認証の実装です。

# Facebookログインを用いて認証する
お次は、Facebookログインとの連携です！！

[Facebook for Developers](https://developers.facebook.com/quickstarts/?platform=android)のクイックスタートに従って、アプリケーションをFacebookに登録します。

登録が済んだら、Googleログインとの連携時と同様に[Firebase console](https://firebase.google.com/console/)でFacebookログインを有効にします。この時に、**アプリケーションID**、**アプリシークレット**が必要になります。先ほどのFacebook for Developersで登録したアプリケーションのページで取得できます。
また今度は、Firebase consoleで生成された**OAuthリダイレクトURI**をFacebook for Developersのアプリケーションページへ設定します。**Facebook for Developersのアプリケーションページの左側にある[+製品を追加]からFacebookログインを追加することで、設定ページを開くことができます。**

それでは、Facebookログインを用いた認証を実装していきましょう。
まず、Facebook SDKを初期化します。

```java
FacebookSdk.sdkInitialize(getApplicationContext());
```

そして、Facebookの`LoginButton`をレイアウトし、設定していきます。

```java
callbackManager = CallbackManager.Factory.create();
FacebookCallback<LoginResult> facebookLoginCallback = new FacebookCallback<LoginResult>() {
        @Override
        public void onSuccess(LoginResult loginResult) {
            firebaseAuthWithFacebook(loginResult.getAccessToken());
        }

        @Override
        public void onCancel() {
        }

        @Override
        public void onError(FacebookException error) {
            if (error != null) {
                DialogManager.createDialog(MainActivity.this, error).show();
            }
        }
    };
LoginButton facebookLoginButton = (LoginButton) findViewById(R.id.facebook_login_button);
facebookLoginButton.setReadPermissions("email", "public_profile");
facebookLoginButton.registerCallback(callbackManager, facebookLoginCallback);
```
`setReadPermissions`メソッドで取得したい情報の権限を設定しておきます。`registerCallback`メソッドでFacebookへのログイン時の`CallbackManager`と`FacebookLoginCallback`を設定します。Facebookへのログイン結果に応じて各コールバックが呼ばれます。

ログインに成功した場合は、Googleログインの時と同様に、FacebookログインのトークンをFirebaseの認証情報と交換し、Firebaseの認証情報を使用してFirebaseでの認証を行います。

```java
private void firebaseAuthWithFacebook(AccessToken token) {
    AuthCredential credential = FacebookAuthProvider.getCredential(token.getToken());
    firebaseAuth.signInWithCredential(credential)
            .addOnCompleteListener(this/*Activity*/, this/*OnCompleteListener*/);
}
```

Firebaseでの認証が成功した場合には、Googleログイン時と同様に`AuthStateListener`の各メソッドがコールバックされます。

また、`onActivityResult`内で、次のように`CallbackManager`の`OnAcvityResult`に値を渡してあげることも忘れないでください。

```java
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    callbackManager.onActivityResult(requestCode, resultCode, data);
}
```

Googleログインの時と流れはだいたい同じですね！
この勢いでどんどん連携プロバイダを増やしていきます。

# Twitterを使用して認証する
今度は色々苦戦したTwitterでの認証です。

まず、[Twitter Application Management](https://apps.twitter.com)にアプリケーションを登録します。

ここで注意して欲しいのが、**Firebaseのログイン情報からメールアドレスを取得したい場合**には、Android StudioにFabricのプラグインが入ってる人でも、プラグイン上からアプリケーションを登録せずに、Twitter Application Management上でアプリケーションを登録してください。

登録が済んだら、Facebookログインとの連携と同じ流れで[Firebase console](https://firebase.google.com/console/)でTwitterを有効にします。この時に、Twitter Application Managementから**API Key**、**API Secret**を取得して、Firebase consoleに設定します。
また同様に、Firebase consoleで生成された**OAuthリダイレクトURI**をTwitter Application Managementのアプリケーションページへ設定してください。

ここまで済んだら、Twitter連携のfirebase認証を実装していきます。

Twitter連携の実装をするにあたり、Android StudioのFabricのプラグインを使用することをお勧めします！これを用いることで、Twitter Loginの実装をすいすいと進めていくことができます。
今回もFabricのプラグインを使用した前提で話を進めていきます。

Fabricのプラグインを起動し、画面に従って登録するアプリケーションを選択していきます。
![](https://c5.staticflickr.com/1/268/31317828620_b255cf19ca.jpg)
この画面で[Twitter]を選択し、[Install]をクリックします。

![](https://c4.staticflickr.com/1/488/31574916331_2801357307.jpg)
この画面になったら、**[I already have a Twitter account]**を選択します。決して、[Create Account]を押しちゃダメですよ！！次の画面で、**Twitter Key**と**Twitter Secret**を求められるので、先ほどのTwitter Application Managementで取得したApi KeyとApi Secretをそれぞれ入力します。
あとは、画面の指示に従って進んでいくのみです！

ここまできたら、GoogleとTwitterの時と同じ流れです。

```java
TwitterAuthConfig authConfig = new TwitterAuthConfig(getString(R.string.twitter_key),
                getString(R.string.twitter_secret));
Fabric.with(this, new Twitter(authConfig));
```
このコードがFabricプラグインによって自動的に追加されているかと思います。

```java
Callback<TwitterSession> twitterLoginCallback = new Callback<TwitterSession>() {
        @Override
        public void success(Result<TwitterSession> result) {
            TwitterSession session = result.data;
            firebaseAuthWithTwitter(session);
        }

        @Override
        public void failure(TwitterException exception) {
            if (exception != null) {
                DialogManager.createDialog(MainActivity.this, exception).show();
            }
        }
    };
twitterLoginButton = (TwitterLoginButton) findViewById(R.id.twitter_login_button);
twitterLoginButton.setCallback(twitterLoginCallback);
```
`TwitterLoginButton`をレイアウトし、コールバックを設定してあげます。
そして、ログイン結果を次のメソッドでハンドリングしています。

```java
private void firebaseAuthWithTwitter(TwitterSession session) {
        AuthCredential credential = TwitterAuthProvider.getCredential(session.getAuthToken().token,
                session.getAuthToken().secret);
firebaseAuth.signInWithCredential(credential)
            .addOnCompleteListener(this/*Activity*/, this/*OnCompleteListener*/);
}
```
ここの処理もGoogleとFacebookの時とだいたい同じです。

TwitterもFacebook同様、`OnAcvitityResult`内で`TwitterLoginButton`の`OnActivityResult`に値を渡す必要があります。

```java
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    twitterLoginButton.onActivityResult(requestCode, resultCode, data);
}
```

Firebaseの認証に成功した場合は、この時も`AuthStateListener`の各メソッドがコールバックされます。

# パスワードベースのアカウントを使用して認証する
最後は、メールアドレスとパスワードをユーザに入力させてFirebase認証を行う場合について説明していきます。

まずは、[Firebase console](https://firebase.google.com/console/)で[Email/password]を有効にします。

この場合は、あらかじめ特別なボタン等が用意されていないので、自分でButtonをレイアウトし、ボタンが押された時の処理を適宜ハンドリングしていきます。

今回は、ボタンを押すと別のActivityが生成され、そのActivity内でユーザにメールアドレスとパスワードを入力してもらうという流れにしました。画面としてはこんな感じです。

![](https://github.com/ronnnnn/FirebaseLoginSample/blob/master/gif/demo2.gif?raw=true)

アプリを始めて使う場合はもちろんアカウントがないので、[sign up]ボタンを選択してもらい、既にアカウントがある場合には[sign in]ボタンを選択してもらいます。
このActivityで入力した情報を最初のActivityの`onActivityResult`で受け取り、Firebaseにその情報を登録していきます。

```java
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == REQUEST_CODE_EMAIL_SIGN_IN && data != null) {
        firebaseAuthWithEmailAndPassword(data);
    } 
}

private void firebaseAuthWithEmailAndPassword(Intent data) {
    if (data.getBooleanExtra(FormActivity.KEY_HAS_ACCOUNT, true)) {
        firebaseAuth.signInWithEmailAndPassword(data.getStringExtra(FormActivity.KEY_EMAIL),
                data.getStringExtra(FormActivity.KEY_PASSWORD))
                .addOnCompleteListener(this/*Activity*/, this/*OnCompleteListener*/);
    } else {
        firebaseAuth.createUserWithEmailAndPassword(data.getStringExtra(FormActivity.KEY_EMAIL),
                data.getStringExtra(FormActivity.KEY_PASSWORD))
                .addOnCompleteListener(this/*Activity*/, this/*OnCompleteListener*/);
    }
}
```
`firebaseAuthWithEmailAndPassword`メソッド内で、[sign in]か[sign up]かを判定して処理を分けています。新しくアカウントを作成する場合は、`createUserWithEmailAndPassword`メソッドを、既にあるアカウントにログインする場合には、`signInWithEmailAndPassword`メソッドを呼びます。引数にはそれぞれ、メールアドレスとパスワードを渡す必要があります。

この時、メールアドレスのフォーマットを、Firebaseの方で判定をしてくれるため、メールアドレスのフォーマットが普通でない(?)場合には、アカウントの作成が弾かれます。

![](https://github.com/ronnnnn/FirebaseLoginSample/blob/master/gif/demo3.gif?raw=true)
優しい世界ですね🍵

Firebaseでメールアドレスとパスワードが認証された場合には、例のコールバックが呼ばれます。
この方法は、他のプロバイダを使用した認証よりは敷居が低いような感じがしますが、自分で入力フォーム等を実装して処理を行わないといけないので、また違った面倒がありますね。

以上が各プロバイダやメールアドレス、パスワードを使ってFirebase認証を実装する一通りの流れになります。
どうでしょう！結構簡単ですね！！ありがたみが強いです！！！

# ログインユーザの情報を取得する
各プロバイダを用いてFirebase認証する方法が分かったので、最後にユーザの情報を取得する方法をまとめておきます。

現在ログイン中のユーザ情報をFirebaseから取得するのは非常に簡単です。
`FirebaseAuth`オブジェクトから`getCurrentUser`メソッドを使用して、得られた`FirebaseUser`オブジェクトからメソッドを使用して各情報を取得するだけです。

例えば、現在ログイン中のユーザの[UID]、[Email]、[Provider]情報を取得したい場合は次のようにします。

```java
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user != null) {
    user.getUid());
    user.getEmail());
    if (user.getProviders() != null && !user.getProviders().isEmpty()) {
        userInfoBundle.putString(ProfileActivity.KEY_USER_PROVIDER, user.getProviders().get(0));
    } else {
        userInfoBundle.putString(ProfileActivity.KEY_USER_PROVIDER, getString(R.string.unknown_user_provider));
    }
}
```
すごく簡単ですね！
現在のところ、FirebaseUserオブジェクトから取れるユーザ情報は、

- display name
- email
- providers
- provider id
- photo url
- provider data
- token
- uid

です。

しかし、これらのユーザ情報が必ずしも取れるとは限りません。各プロバイダの設定で権限を適切に設定しておく必要があります。
今回は、Twitterからemail情報を取得しようとした時に苦戦してしまったので、その取得方法をここに書いておきます。

## Twitterからユーザのメールアドレスを取得する
先ほど説明した方法でTwitterからユーザのメールアドレスを取得しようとしても、取得することができません。そこで、[Twitter Application Management](https://apps.twitter.com)の設定を変更します。

自分の登録したアプリケーションのページを開き、[Permissions]タブを選択します。
ここで、emaiのパーミッションを解放する必要があります。
>These additional permissions require that you provide URLs to your application or service's privacy policy and terms of service. You can configure these fields in your **Application Settings**.

とあるので、プライバシーポリシーと利用規約をApplication Settingsのページで設定しておく必要があります。今回仮で自分のTwitterのURLを登録したのですが、特に問題なかったです。
これらを設定したら、[Request email addresses from users]にチェックをして、パーミッションをアップデートします。

現在、Fabricプラグインから登録したアプリケーションの場合、このメールアドレスの権限を取得するのが難しくなっています。一方で、Twitter Application Managementから登録したアプリケーションであれば、この権限を取得するのが容易です。Fabricプラグインからアプリケーションを登録しないように言ったのはこのためです。
今回Twitterからメールアドレスの権限をリクエストするために[Twitter Support](https://support.twitter.com/forms/platform)に問い合わせをしたところ、次のようなメールが自動返信されてきました。

![](https://c1.staticflickr.com/1/686/31544229192_6181c6bb87.jpg)

このパーミッションをアップデートすることで、Firebaseにユーザのアドレスが登録されるようになるはずですが、このままでは単に`FirebaseUser`オブジェクトからユーザのメールアドレスを取得しようとしても、できません。

そこで、アプリケーション側では次の処理を行う必要があります。

```java
TwitterSession session = Twitter.getSessionManager().getActiveSession();
TwitterAuthClient authClient = new TwitterAuthClient();
authClient.requestEmail(session, new Callback<String>() {
    @Override
    public void success(Result<String> result) {
    
    }

    @Override
    public void failure(TwitterException exception) {

    }
});
```
正しくリクエストが行えた場合、この画面が出てきます。

![](https://c7.staticflickr.com/6/5567/31653661806_c95b139591.jpg)

リクエストの結果に合わせて各コールバック内で処理を行えばいいです。

# 最後に
今回はFirebase Authenticationを用いてアプリに認証機能を追加する方法を、つまづいたポイントを踏まえて説明しました。
めちゃくちゃ長くなってしまい、申し訳ないです。
初めてのQiitaパブリック投稿だったので、大目に見ていただけると幸いです🙇

今回作成したサンプルアプリはGitHubで公開していますので、ご参考までにどうぞ。
[FirebaseLoginSample](https://github.com/ronnnnn/FirebaseLoginSample)
各種keyの設定をお忘れなく。

Firebase認証と連携できるプロバイダはこれらだけでなく、GitHubもあります。カスタム認証を使用することで、LINEと連携させることもできるようです。Google Developers Japanのブログに記事がありますので、[こちら](https://developers-jp.googleblog.com/2016/11/authenticate-your-firebase-users-with-line-login.html)をご参考ください。

今回お話したFirebaseの機能は氷山の一角に過ぎないので、Firebaseをもっともっと使い倒していきたいですね！！

それでは、良い年末をお過ごしください！

# 参考
- [Firebase Documentations](https://firebase.google.com/docs/)
- [Firebase Authentication Documentations](https://firebase.google.com/docs/auth/)
- [Google Sign-In for Android](https://developers.google.com/identity/sign-in/android/sign-in)
- [Facebook Login Documentations](https://developers.facebook.com/docs/facebook-login/android)
- [Twitter Developer Documentations](https://dev.twitter.com/web/sign-in/implementing)
- [Fabric Documentations](https://docs.fabric.io/android/twitter/log-in-with-twitter.html)