Go×GAE×Dockerで作るGoogleOAuth認証アプリ

Dockerでローカル環境を楽に整えつつ、GAEをベースにしたアプリ開発ができれば最高だなあ。と常々思っていたので、今回はそれについて書きつつ、実践的なGoogleOAuth認証のアプリを作っていきたいと思います。

すぐにでもコードを見たい方はこちらをどうぞ。

https://github.com/gotokatsuya/gae-google

# GAEとdepのDocker環境を整える

まずは DockerCompose をインストールしましょう。

[公式サイト](https://docs.docker.com/compose/install/)を参考にインストールしてください。

インストールできたら次はDockerイメージとComposeファイルを用意します。

今回はGAEベースのアプリケーションを作りたいので、mercariさんが提供してくれているGEAのDockerイメージを利用します。

加えて、ベンダリングのツールには「dep」を利用するので、いい感じのDockerイメージを探してきてComposeファイルに追加します。

```docker-compose.yaml
version: '2'
services:
  app:
    image: mercari/appengine-go:1.8
    working_dir: /go/src/github.com/gotokatsuya/gae-google
    command: make serve-dev
    ports:
      - 8080:8080
      - 8000:8000
    volumes:
      - .:/go/src/github.com/gotokatsuya/gae-google
  dep:
    image: instrumentisto/dep:0.3.2
    working_dir: /go/src/github.com/gotokatsuya/gae-google
    volumes:
      - .:/go/src/github.com/gotokatsuya/gae-google
```

このようなdocker-compose.ymlを用意するだけでローカル開発が最高に楽になります。

# GoogleOAuth認証アプリを作る

## GoogleOAuth認証設定

コードを書く前にGoogleOAuthの設定をして、ClientIDとClientSecretを取得、そしてRedirectURLをセットします。設定方法は[公式サイト](https://developers.google.com/identity/protocols/OpenIDConnect)が参考になります。

今回のアプリケーションではRedirectURLは`http://localhost:8080/auth/google/callback`を指定してください。ClientIDとClientSecretはapp.yamlに環境変数として記載します。

```app.yml
application: gae-google
version: 1
runtime: go
api_version: go1.8
handlers:
- url: /.*
  script: _go_app
  secure: always
env_variables:
  AUTH_GOOGLE_CLIENT_ID: "xxx"
  AUTH_GOOGLE_CLIENT_SECRET: "xxx"
  AUTH_GOOGLE_REDIRECT_URL: "http://localhost:8080/auth/google/callback"
```

## 実際にコードを書いていく

まずはRoutingから作ります。


```routes.go
package routes

import (
	"net/http"

	"github.com/gorilla/mux"
	"github.com/urfave/negroni"

	"github.com/gotokatsuya/gae-google/app/controllers"
)

var (
	App = negroni.New(negroni.NewRecovery())
)

func init() {
	router := mux.NewRouter()

	authRouter := router.PathPrefix("/auth").Subrouter()
	authRouter.Path("/login").HandlerFunc(controllers.AuthLoginHandler).Methods(http.MethodGet)
	authRouter.Path("/google").HandlerFunc(controllers.AuthGoogleHandler).Methods(http.MethodGet)
	authRouter.Path("/google/callback").HandlerFunc(controllers.AuthGoogleCallbackHandler).Methods(http.MethodGet)

	App.UseHandler(router)
}
```

Contollerを作っていきます。

```controller.go
package controllers

import (
	"net/http"

	"google.golang.org/appengine"

	"github.com/gotokatsuya/gae-google/lib/template"

	"github.com/gotokatsuya/gae-google/app/services/auth"
)

// AuthLoginHandler HTMLをレンダリングする
func AuthLoginHandler(w http.ResponseWriter, r *http.Request) {
	template.Render.HTML(w, http.StatusOK, "auth/login", nil)
}

// AuthGoogleHandler Googleログイン
func AuthGoogleHandler(w http.ResponseWriter, r *http.Request) {
	svc := auth.NewService()
    // ランダム値を生成する
	state := svc.GenerateState()
    // CallbackするURLを生成する
	url := svc.GoogleAuthURL(state)
    // 生成したランダム値ををセッションに保存する
	svc.SaveState(state, r, w)
	http.Redirect(w, r, url, http.StatusFound)
}

// AuthGoogleCallbackHandler Googleログイン認証結果
func AuthGoogleCallbackHandler(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)
	svc := auth.NewService()
    // セッションに保存されているランダム値と比較して一致するかどうか確認
	if r.URL.Query().Get("state") != svc.State(r) {
		http.Error(w, "invalid state", http.StatusUnauthorized)
		return
	}
    // GoogleログインしたユーザーのメールアドレスやIDを取得する
	if err := svc.GoogleLogin(ctx, r.URL.Query().Get("code")); err != nil {
		http.Error(w, err.Error(), http.StatusUnauthorized)
		return
	}

    // 適当な画面に飛ばす
	http.Redirect(w, r, "https://eure.jp", http.StatusFound)
}
```

# DockerComposeを使って起動する

最初に記載したように今回はdepを使って依存ライブラリをベンダリングします。今回はvendorフォルダをgit管理しているので実行する必要はないですが、initを実行するとvendorフォルダが作られ、ライブラリをベンダリングしてくれるようになります。

```sh
// $ docker-compose run --rm dep init
```

GAEに載せるアプリケーションを起動します。

```shell
$ docker-compose up app
```

起動後に http://localhost:8080/auth/login へアクセスするとこのようなシンプルな画面がレンダリングされるはずです。

<img width="1327" alt="スクリーンショット 2017-12-09 0.17.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/73483/90fb67e1-1552-9602-6320-1253f71c0f9a.png">

「Google Login」ボタンを押下するとログイン画面が立ち上がります。ログインに成功すると、適当に設定したeurekaのHPに遷移します。

<img width="1336" alt="スクリーンショット 2017-12-09 0.35.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/73483/51348d13-b480-7f9d-3a38-ae8d1297b220.png">