Heroku と Go での OAuth2
最終更新日 2022年01月26日(水)
このチュートリアルでは、ユーザーが Heroku プラットフォームの OAuth API を使用して認証を行い、api.heroku.com の API 呼び出しを実行できる Web アプリケーションを Heroku で作成する方法を示します。このアプリケーションは、より複雑な統合シナリオの基礎として機能します。
チュートリアルでは Go 言語を使用しますが、主な概念、設定、アーキテクチャは、Heroku でサポートされている他の言語にも容易に応用できます。
デモアプリケーションのサンプルコードは GitHub で入手できます。編集や機能強化を歓迎します。単にリポジトリをフォークし、変更を加えて、プルリクエストを送信してください。
前提条件
- Heroku ユーザーアカウント。 無料ですぐにサインアップできます。
- まだ Heroku に詳しくない場合、「Heroku スターターガイド (Go)」を参照して理解を深めます。
サンプルアプリケーション
サンプルアプリでは、承認と API 呼び出し実行のメインコンポーネントを示しています。このチュートリアルの残りの部分では、アプリのさまざまな部分にハイライトを当てます。
はじめに、アプリケーションを複製します。
$ go get -u github.com/heroku-examples/heroku-oauth-example-go
$ cd $GOPATH/src/github.com/heroku-examples/heroku-oauth-example-go
OAuth クライアントの設定
$ heroku clients:create "Go OAuth Example ($USER)" https://go-heroku-oauth-example-$USER.herokuapp.com/auth/heroku/callback
提供されている HEROKU_OAUTH_ID
および HEROKU_OAUTH_SECRET
出力は以下の手順で使用します。これらは、サンプルアプリケーションを識別し、Heroku に対して認証するために使用されます。
アプリの設定
$ heroku create go-heroku-oauth-example-$USER
$ heroku labs:enable runtime-dyno-metadata
$ heroku config:add HEROKU_OAUTH_ID=<value from above>
$ heroku config:add HEROKU_OAUTH_SECRET=<value from above>
$ heroku config:add COOKIE_SECRET=`openssl rand -hex 32`
$ heroku config:add COOKIE_ENCRYPT=`openssl rand -hex 16`
$ git push heroku master
$ heroku open
COOKIE_*
環境設定は、サンプルアプリによって作成された Cookie をセキュリティで保護するために使用されます。
この時点で、アプリケーションが動作し、Web ブラウザで “Sign in with Heroku” (Heroku にサインイン) ページが開いています。
次に進む前に、サンプルアプリケーションの裏側のコードを見てみましょう。
グローバル変数
これは単純なアプリケーションの例なので、いくつかのグローバル変数を使用します。通常のアプリケーションでこれらを定義するのは、http.Handler を実装する構造体か、フレームワークに固有である同様の構造体です。
var (
store = sessions.NewCookieStore([]byte(os.Getenv("COOKIE_SECRET")), []byte(os.Getenv("COOKIE_ENCRYPT")))
oauthConfig = &oauth2.Config{
ClientID: os.Getenv("HEROKU_OAUTH_ID"),
ClientSecret: os.Getenv("HEROKU_OAUTH_SECRET"),
Endpoint: heroku.Endpoint,
Scopes: []string{"identity"},
RedirectURL: "http://" + os.Getenv("HEROKU_APP_NAME") + "herokuapp.com/auth/heroku/callback",
}
stateToken = os.Getenv("HEROKU_APP_NAME")
)
func init() {
gob.Register(&oauth2.Token{})
store.MaxAge(60 * 60 * 8)
store.Options.Secure = true
}
store
は Cookie ベースのセッションストアです。サンプルアプリでは、その中間データを、セキュリティで保護された Cookie に保存します。
oauthConfig
は、サンプルアプリケーションのインスタンス用の OAuth2.Config 設定です。サポートされているスコープについての詳細は、こちらを参照してください。
stateToken
は OAuth2 ハンドシェイク中に使用されます。これは通常、OAuth2 ハンドシェイク中にアプリケーションによってチェックできる不明瞭な値に設定されます。
init 関数
init 関数は、次のことを行います。
oauth2.Token
タイプをgob
に登録して、受け取るトークンを後で Cookie にシリアル化できるようにします。- 8 時間で期限が切れるように
store
を設定して、Cookie が HTTPS 経由でしか送信されないようにします。
ハンドラー
func handleRoot(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, `<html><body><a href="/auth/heroku">Sign in with Heroku</a></body></html>`)
}
handleRoot
関数は、単純なインデックスページを表示します。
func handleAuth(w http.ResponseWriter, r *http.Request) {
url := oauthConfig.AuthCodeURL(stateToken)
http.Redirect(w, r, url, http.StatusFound)
}
handleAuth
関数は oauthConfig
を使用して、Heroku の同意ページを指す URL を作成した後、ユーザーをこのページにリダイレクトします。これは OAuth 対話の最初の部分です。
func handleAuthCallback(w http.ResponseWriter, r *http.Request) {
if v := r.FormValue("state"); v != stateToken {
http.Error(w, "Invalid State token", http.StatusBadRequest)
return
}
ctx := context.Background()
token, err := oauthConfig.Exchange(ctx, r.FormValue("code"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
session, err := store.Get(r, "heroku-oauth-example-go")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
session.Values["heroku-oauth-token"] = token
if err := session.Save(r, w); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/user", http.StatusFound)
}
handleAuthCallback
は OAuth 対話の 2 番目かつコアの部分です。まず state
フォームの値が取得され、設定済みの stateToken
と照合されます。同じでない場合、リクエストは不正です。次に、アプリはバックグラウンドコンテキスト (実際のアプリケーションではアプリケーション固有のコンテキストが使用されます) を使用して OAuth2 交換を実行し、提供された code
をトークンに変換します。次に、アプリはトークンをセッションに格納し、セッションを Cookie に保存します。最後に、/user
ルートにリダイレクトされます。
func handleUser(w http.ResponseWriter, r *http.Request) {
session, err := store.Get(r, "heroku-oauth-example-go")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
token, ok := session.Values["heroku-oauth-token"].(*oauth2.Token)
if !ok {
http.Error(w, "Unable to assert token", http.StatusInternalServerError)
return
}
client := oauthConfig.Client(context.Background(), token)
resp, err := client.Get("https://api.heroku.com/account")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer resp.Body.Close()
d := json.NewDecoder(resp.Body)
var account struct { // See https://devcenter.heroku.com/articles/platform-api-reference#account
Email string `json:"email"`
}
if err := d.Decode(&account); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, `<html><body><h1>Hello %s</h1></body></html>`, account.Email)
}
handleUser
関数は、トークンとのペアで oauth2.Client
メソッドを使用して Heroku API を呼び出す方法を示しています。この関数ではまず、セッションからトークンを取得します。次に、[oauth2.Client](https://godoc.org/golang.org/x/oauth2#Config.Client)
メソッドを使用して http.Client を作成し、Heroku API からユーザーのアカウント情報をフェッチします。最後に、ユーザーに hello と表示します。
func main() {
http.HandleFunc("/", handleRoot)
http.HandleFunc("/auth/heroku", handleAuth)
http.HandleFunc("/auth/heroku/callback", handleAuthCallback)
http.HandleFunc("/user", handleUser)
http.ListenAndServe(":"+os.Getenv("PORT"), nil)
main 関数
main
関数はプログラムのエントリポイントであり、前出のハンドラーをデフォルトの HTTP Mux に接続して、$PORT
上の HTTP サーバーを起動します。
アプリへのアクセス
Sign in with Heroku
(Heroku にサインイン) をクリックすると、ブラウザにより /auth/heroku
がリクエストされ、OAuth2 プロバイダーの URL が生成され、ログインのために https://id.heroku.com
にリダイレクトされます。サンプルアプリのインスタンスを承認すると、id.heroku.com
により、OAuth2 の適切な state
および code
パラメータを使用してサンプルアプリの /auth/heroku/callback
にリダイレクトされます。最後に、Hello <your email address>
と表示するページにリダイレクトされます。
まとめ
このチュートリアルでは、Heroku の OAuth2 API を使用して認証を行う Heroku Web アプリケーションを作成する方法と、提供された OAuth2 トークンを使用して Heroku の API に対して認証を行う方法を示しました。