Go で OAuth2.0

Posted on Sep 2, 2018


Go 学習のため、Go を使ってウェブアプリを作ってみた際、困ったことをまとめておこうと思います。これまで、Python + Flask でウェブをスクレイピングしてJSONを返す、みたいなのは作ったことあったのですが、型のある言語でのウェブアプリ作成は初めてになります。

題材としては今回、「認可サーバー」を選んでいます。現職にて、事業開発(というか営業)として認可ソリューションを売っている事もあり、OAuth2.0 への理解をもう少し深めたい、というのがモチベーションになります。教材として、『OAuth 2 in Action』を使っています。

OAuth 2 in Action

Manning 出版より2017年に出版された、OAuth2.0 を理解するために書かれた本です。認可サーバー側だけでなく、クライアント側、リソースサーバー側の視点からも OAuth2.0 のフローについて書かれた本であり、認可サーバーをスクラッチで実装するために書かれた本、というよりは、OAuth2.0 を正しく理解し既存のソリューションを正しく使いましょう、というニュアンスの内容になります。実際、最後のあとがき部分にて、“We know that almost no one reading this book will implement an OAuth ecosystem from scratch, end to end. ” と書かれています。

本に付属のサンプルコードは、Javascript (Node.js)で書かれており、非常にシンプルでわかりやすいです。この本を一通り読み、コードを触ったおかげで、難解だった RFC6749 もスラスラ読めるようになりました。同時に、上記のサンプルコードが、OAuth2.0 を理解してもらうためのものであり、実戦で使うためにはまだまだ工夫が必要だろうな、というのもよくわかりました。出版されるかどうかわかりませんが、日本語版が出たら、お客さんに配って回りたい気分です。

Go での実装

上記のサンプルコードをただ眺めるだけでは理解に時間がかかるだろうな、と思った事もあり、学習中の Go を使って写経を行ってみました。Go を使ってウェブアプリを作るのは初めてだった事もあり、エラーハンドリング等で色々悩みました。

困った事1:フレームワーク選び

Python であれば、Django や Flask といった有名フレームワークがあり、選択が楽だったのですが、Go では、ginecho など数え切れないくらい種類があり、どれがベストかわからず。高速な事で有名な httprouter の利用も考えましたが、後述のエラーハンドリングを考えると使いづらかったため、今回は、標準ライブラリだけでの実装を選択しました。

困った事2:エラーハンドリング

エラーハンドリングについては、公式ドキュメントを参考にしました。

Error handling in Go

ただ、これだけでは、グローバル変数を扱いづらかったため、下記のブログを参考に修正を加えました。

http.Handler and Error Handling in Go

出来上がったコードはこんな感じです。あとは main() のところで、http.Handleを使い、パスと関数を登録していくだけになります。標準ライブラリだけを使って、すっきりした形で書けるようになった気がします。

// クライアントに送り返す内容
type responseContent interface {
	jsonify() ([]byte, error)
}

// クライアントに返すデータ(またはエラー)とHTTPステータスコード
type response struct {
	content responseContent
	code    int
}

// env contains global parameters for a web application
type env struct {
	template *template.Template
    //DBのアドレスとか、その他グローバル変数はここへ
}

// handlerFunc is a function that can be registed to a router
type appHandlerFunc func(http.ResponseWriter, *http.Request, *env) *response

// appHandler is a struct that contains appContext and an original appHandler
type appHandler struct {
	env         *env
	handlerFunc appHandlerFunc
}

// ServeHTTP makes the handler implement the http.Handler interface
func (h *appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	//Response a JSON if catch an error
	if res := h.handlerFunc(w, r, h.env); res != nil {

		// Jsonify the response. Return a error if failed to create a JSON.
		result, err := res.content.jsonify()
		if err != nil {
			http.Error(w, "Internal Error", http.StatusInternalServerError)
			log.Println(err)
			return
		}

		// Send back JSON
		w.WriteHeader(res.code)
		w.Header().Set("Content-Type", "application/json")
		w.Header().Set("Cache-Control", "no-store")
		w.Header().Set("Pragma", "no-cache")
		w.Write(result)
	}
}

困った事3:パッケージ、ファイルの構造

これに関してはいまだに理解できていない部分が多くありますが、下記のブログ等を参考にしました。今回書いたコードは利用シーンを想定いないので、正直適当です。

Structuring Go API Successful Go Program Design Go Project Layout

まとめ

まだまだレベルは低いですが、Go を使ってウェブアプリは作れるようにはなりました。今後は、goDoc なんかも意識しつつ、また別の物を作ってみようと思っています。より良いベストプラクティス的なものがあれば、教えていただけると嬉しいです。

なお、今回作った認可サーバーはこちらです。色々途中なのですが、十分目的は達したので作りかけのまま放置予定です。


comments powered by Disqus