Help us understand the problem. What is going on with this article?

Firebase AuthenticationとGAEで30分くらいでログイン機能を実装してみる

TL;DR

今からFirebase Authenticationを使ってソーシャルログイン機能を30分くらいで検証用に実装します。
各種SNSのデベロッパーサイトへの登録はしてあるものとします。
要件として作成するのは、トップページと、ログイン後ページの2画面のシンプルな構成。

スクリーンショット 2019-12-03 22.13.11.png

コードは置いといた。(secret.yamlは準備してくれ・・・)
https://github.com/spre55/firebase-auth-sample/

今回使う主な技術・ツール・サービス

  • HTML/CSS/JavaScript
  • Go 1.13
  • Firebase Authentication
  • Google App Engine (Standard Environment)

Google App Engine(以下GAE)はGCP上で提供されるPaaSです。
デプロイ時にバージョンを指定でき、トラフィックを別バージョンに切り返すことで、Blue/Greenデプロイなども簡単に行えます。
今回はGAEのStandard Environmentを使います。Goをサポートしているので、バックエンドはGoを使ってみます。

更に、今回はソーシャルログイン機能ということで、認証を簡単に実装できるようにしてくれるFirebase Authenticationを使います。
アプリはもちろん、クライアントSDKはWebもサポートしているので、JavaScriptを使用して、簡単に認証機能を実装することができます。複雑な要件がなければ、ドキュメントのコピペで終わることでしょう。

実装する機能

  • メール/パスワードサインアップ・ログイン
  • Twitterログイン
  • Googleログイン
  • Facebookログイン
  • Githubログイン
  • ログアウト

アプリケーションの構築

今回は以下のようなシステム構成を目指します。
赤い部分が我々がコードで表現するアプリケーション部分です。
それ以外の部分は各種コンソール画面から、ボタンポチポチで実現できます。

firebase_gae.png

各種ソーシャルプロバイダの登録

まずは以下のソーシャルプロバイダのデベロッパーサイトへ登録し、APIキーなどを使用可能な状態にしてください。詳細な手順は省きます。

GAE/Go で Hello World !

以下からGoogle Consoleにログインしてチュートリアルを行えば、10分経たずで全世界にHello Worldできるかと思います。(実際にタイムアタックを試したら、クレジットカード登録も含め8分程度でできました)
https://cloud.google.com/appengine/?hl=ja

まずは、チュートリアルを終え、GoのアプリケーションをGoogle App Engineでデプロイしてみましょう。話はそれから。

GCPは一年間の無料期間が設定されてます。まだ触ってない人はこれを機にいじってみましょう。

ドキュメントに記載されているクイックスタートにもあると思いますが、公式のGithubアカウントにて、最小構成のhelloworldアプリやその他もろもろが公開されているので、これをそのままクローンしてデプロイできますね。
https://github.com/GoogleCloudPlatform/golang-samples/tree/master/appengine/go11x/helloworld

Firebase Consoleでの認証の設定

https://console.firebase.google.com/u/0/

Firebaseのクラウドコンソールにアクセスしてみましょう。

スクリーンショット 2019-12-03 2.46.54.png

"ログイン方法"のタブにいくと、各種プロバイダのログイン方法が指定できますので、必要なものを有効にしましょう。今回は最初に述べた、「メール/パスワード」、「Google」、「Twitter」、「Facebook」、「Github」の5つを有効にしましょう。
ここで有効にするために、プロバイダによっては、APIキー及び、シークレットキーなどを求められるので、最初に各種デベロッパーサイトで設定して入手したAPIキーとシークレットキーを入力しましょう。

スクリーンショット 2019-12-03 2.47.23.png

とりあえず画面を2つつくる

すでに作成したGAEにてHello Worldを表示するアプリケーションが存在する前提で進みます。

とりあえずGoからサーバサイドレンダリングでHTMLの内容を表示するだけでいいので、一旦、今回作りたいトップページと、ログイン後ページのガワを作ります。

構成はこんな感じ。

├── app.yaml
├── main.go
└── templates
    ├── my.html
    └── signup.html

各ファイルは以下の通り。

app.yaml
runtime: go113

templateディレクトリを作り、その配下に
サインアップ前のトップページと、

signup.html
<!DOCTYPE html>
<html>
<body>
    <h1>Welcome to Sign Up Page!</h1>
</body>
</html>

サインアップ後に入れるマイページを作って、

my.html
<!DOCTYPE html>
<html>
<body>
    <h1>Welcome to My Page!</h1>
</body>
</html>

main.goには、ルーティングまわりを記述。

main.go
package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
    "text/template"
)

func main() {
    http.HandleFunc("/", indexHandler)
    http.HandleFunc("/my", myHandler)

    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
        log.Printf("Defaulting to port %s", port)
    }
    log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
}

// indexHandler ... サインアップページ(トップページ)
func indexHandler(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path != "/" {
        http.NotFound(w, r)
        return
    }

    t := template.Must(template.ParseFiles("templates/signup.html"))
    if err := t.ExecuteTemplate(w, "signup.html", nil); err != nil {
        log.Fatal(err)
    }

}

// myHandler ... サインアップ後のマイページ
func myHandler(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path != "/my" {
        http.NotFound(w, r)
        return
    }

    t := template.Must(template.ParseFiles("templates/my.html"))
    if err := t.ExecuteTemplate(w, "my.html", nil); err != nil {
        log.Fatal(err)
    }
}

https://github.com/spre55/firebase-auth-sample/tree/feature/20191203_page_only

GAEにデプロイして、動作するようならオッケー。

Firebase を使える状態にする

Firebaseを使えるようにするため、まずはクライアントSDKをCDNで呼ぶ。そのためにFirebaseコンソールからコードをコピってきてアプリケーション内に埋め込みたい。

まずFirebaseコンソールプロジェクトの設定をみる。

スクリーンショット 2019-12-03 3.24.37.png

Settingページへ遷移するので、「アプリを追加」ボタンを押す。

スクリーンショット 2019-12-03 3.25.15.png

今回はWebアプリなのでWebを選択。

スクリーンショット 2019-12-03 3.25.25.png

適当にアプリの名前を決めると、以下のような画面になるので、ここで表示されたコードを、自分のアプリケーションに埋め込む。(今回はFirebase Hostingは使わないので未チェックでOK)

スクリーンショット 2019-12-03 3.25.51.png

アプリケーションに上記のコードを埋め込んで動作させる

上記コードはGithubで公開とかしないならベタ書きでもいいのですが、あんまり見られたくないので、secret.yamlに環境変数として記述し、それをapp.yamlで読み込むようにします。
また、secret.yamlをGithubに公開したら意味ないので、.gitignoresecret.yamlを記載して、git管理対象から除外します。
複数人で共有する場合は、secret.yamlを暗号化するか、別の仕組みを使うかする必要がありますが、今回のアプリケーションではそこまではやりません。

最終的なディレクトリ構造はこんな感じ。

├── app.yaml
├── go.mod
├── main.go
├── secret.yaml
├── .gitignore
└── templates
    ├── my.html
    └── signup.html

secret.yamlはこんな感じで書いておいて、

secret.yaml
env_variables:
  API_KEY: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
  AUTH_DOMAIN: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.firebaseapp.com"
  DATABASE_URL: "https://XXXXXXXXXXXXXXXXXXXXXXX.firebaseio.com"
  PROJECT_ID: "XXXXXXXXXXXXXXXXXXXXXXX"
  STORAGE_BUCKET: "XXXXXXXXXXXXXXXXXXXXXXX.appspot.com"
  MESSAGING_SENDER_ID: "XXXXXXXXXXXXXXXX"
  APP_ID: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

app.yamlでそれを読み込みます。

app.yaml
env_variables:
runtime: go113

includes:
- secret.yaml

secret.yaml.gitignoreに。

secret.yaml
secret.yaml

次にmain.go内にてos.Getenvで環境変数を読み込み,signup.htmlmy.htmlに渡したいので、

main.go
 ... ()
    if err := t.ExecuteTemplate(w, "signup.html", getEnvVars()); err != nil {
 ... ()

func getEnvVars() map[string]string {
    return map[string]string{
        "apiKey":            os.Getenv("API_KEY"),
        "authDomain":        os.Getenv("AUTH_DOMAIN"),
        "databaseURL":       os.Getenv("DATABASE_URL"),
        "projectId":         os.Getenv("PROJECT_ID"),
        "storageBucket":     os.Getenv("STORAGE_BUCKET"),
        "messagingSenderId": os.Getenv("MESSAGING_SENDER_ID"),
        "appId":             os.Getenv("APP_ID"),
    }
}

signup.html
...(略)
    <script>
        const config = {
            apiKey: "{{.apiKey}}",
            authDomain: "{{.authDomain}}",
            databaseURL: "{{.databaseURL}}",
            projectId: "{{.projectId}}",
            storageBucket: "{{.storageBucket}}",
            messagingSenderId: "{{.messagingSenderId}}",
            appId: "{{.appId}}",
        };

        firebase.initializeApp(config);
...()

こんな感じ。

各種機能の実装

ここまでで、各プロバイダとFirebaseを使う準備はできました。
あとは機能の実装です。

今回実装するのは以下の機能でした。

  • メール/パスワードサインアップ・ログイン
  • Twitterログイン
  • Googleログイン
  • Facebookログイン
  • Githubログイン
  • ログアウト

firebaseuiを使って実装してみたいと思います。

signup.html にて、uiConfig.signInOptionsに各種PROVIDER_IDを指定してあげれば、ソッコーで
ログインボタンのUIが出来上がります。

signup.html
...(略)
        const uiConfig = {
            callbacks: {
                signInSuccessWithAuthResult: function (authResult, redirectUrl) {
                    return true;
                },
                uiShown: function () {
                    document.getElementById('loader').style.display = 'none';
                }
            },
            signInFlow: 'popup',
            signInSuccessUrl: 'my',
            signInOptions: [
                firebase.auth.GoogleAuthProvider.PROVIDER_ID, // Google
                firebase.auth.FacebookAuthProvider.PROVIDER_ID, // Facebook
                firebase.auth.TwitterAuthProvider.PROVIDER_ID, // Twitter
                firebase.auth.GithubAuthProvider.PROVIDER_ID, // Github
                firebase.auth.EmailAuthProvider.PROVIDER_ID, // Email/Password
            ],
        };
        // サインアップ/サインインフォームのUI
        const ui = new firebaseui.auth.AuthUI(firebase.auth());
        ui.start('#firebaseui-auth-container', uiConfig);
...(略)

firebaseuiを使用すると、自分でCSSとかをいろいろいじったりする必要はありません。
スクリーンショット 2019-12-03 4.08.11.png

サインイン状態は以下のように、firebase.auth().onAuthStateChanged()を使用してとってこれます。
ログアウトも、firebase.auth().signOut()を呼ぶだけですね。

my.html
... (略)
        // ユーザのサインイン状態の判定
        firebase.auth().onAuthStateChanged(function (user) {
            if (user) {
                // サインインできてるとき
                console.log('sign in');
            } else {
                // No user is signed in.
                console.log('sign out');
                location.href = "/";
            }
        });

        // ログアウト
        function signOut() {
            firebase.auth().signOut().then(function () {
                console.log('success sign out.');
            }).catch(function (error) {
                console.log('error: ', error);
            });
        }
... (略)

ログインページができました。
まあ、検証用では十分ですね。

まとめ

GoogleAppEngineとFirebaseAuthenticationを使うと簡単にソーシャルログイン機能が作れます。
レンタルサーバーとか借りようと思ってる人、ためしに使ってみては?

困ったら

信頼性の高い公式ドキュメントを見ましょう。
ただし翻訳のタイムロスが無い分、ドキュメントは日本語のものより英語のもののほうが情報が新しかったりするので注意してください。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした