LoginSignup
29

More than 5 years have passed since last update.

Google App Engine for GoでOAuth2による認証を用意する

Last updated at Posted at 2016-04-12

要点

Google App EngineでGoogle AccountによるOAuth2認証を用意します。
実例のあるOpenID Connectを使用します。
App Engineは基本無料なので安心して使えます。
Enable Billingのようなものが出てきたら速攻で脱出してください :scream:

OAuth2 for App Engine

GoでのOAuth認証手順。
https://cloud.google.com/appengine/docs/go/oauth/

実例があるOpenID Connectで用意します
https://developers.google.com/identity/protocols/OpenIDConnect

Google Developer ConsoleでAPI Credentialを取得

  1. https://console.cloud.google.com
  2. サイドバーのAPI Managerをクリック
  3. Credentialsをクリック
  4. Create credentialsをクリック
  5. OAuth client IDをクリック
  6. Web applicationを選択後、createをクリック
  7. 作成されたclientをクリックしてClient IDClient secretをメモ
  8. 念のためAuthorized redirect URIsにエンドポイントを指定する

実装

使用したLibraryはこちらになります。
未インストールのものはgo get -u -vしてください。

import (
    "fmt"
    "net/http"
    "strings"

    "github.com/satori/go.uuid"
    "golang.org/x/oauth2"
    v2 "google.golang.org/api/oauth2/v2"
    "google.golang.org/appengine"
    appenginelog "google.golang.org/appengine/log"
)

まずは、OAuth2用のConfigを作成します。

var (
    conf = oauth2.Config{
        ClientID:     "先ほどのClient ID",
        ClientSecret: "先ほどのClient secret",
        Scopes:       []string{"openid", "email", "profile"}, // 6/19 update: openidのscopeが漏れていたので追加
        Endpoint: oauth2.Endpoint{
            AuthURL:  "https://accounts.google.com/o/oauth2/v2/auth",
            TokenURL: "https://www.googleapis.com/oauth2/v4/token",
        },
    }
    // 後で使います
    // 6/11 update:globalで持つのは誤りでした
    state string
)

次に認証用のhandlerを作成します。

func init() {
    http.HandleFunc("/api/oauth2", oauth2Handler)
    http.HandleFunc("/oauth2callback", tokenHandler)
}

func oauth2Handler(w http.ResponseWriter, r *http.Request) {
    // ランダムな文字列作成に
    // github.com/satori/go.uuid
    // を使用しています
    state = uuid.NewV4().String()

    // 6/11 update:stateをredirect後と比較する場合はcookieに入れるのが無難です
    sc := &http.Cookie{
        Name:   "hogehoge",
        Value:  state,
        MaxAge: 60,
        Path:   "/",
    }
    redierctURL := getRedirectURL(r.URL.Host)
    conf.RedirectURL = redierctURL
    url := conf.AuthCodeURL(state)
    http.Redirect(w, r, url, 302)
}

func getRedirectURL(host string) string {
    return fmt.Sprintf("https://%s/oauth2callback", host)
}

次に、トークン用のhandlerを作成します。

func tokenHandler(w http.ResponseWriter, r *http.Request) {
    // redirectされたstateと生成したstateが等しいかを確認します
    // 6/11 update:cookieと比較するようにします
    sc, err := r.Cookie("hogehoge")
    if err != nil ||  sc.Value != r.FormValue("state") {
        http.Error(w, "state is invalid.", http.StatusUnauthorized)
        return
    }

    // 認証コードを取得します
    code := r.FormValue("code")
    // appengineのcontextを取得します
    context := appengine.NewContext(r)

    // 認証コードからtokenを取得します
    tok, err := conf.Exchange(context, code)
    if err != nil {
        http.Error(w, err.Error(), http.StatusUnauthorized)
        return
    }

    // tokenが正しいことを確認します
    if tok.Valid() == false {
        http.Error(w, "token is invalid.", http.StatusUnauthorized)
        return
    }

    // oauth2 clinet serviceを取得します
    // 特にuserの情報が必要ない場合はスルーです
    service, err := v2.New(conf.Client(context, tok))
    if err != nil {
        http.Error(w, err.Error(), http.StatusUnauthorized)
        return
    }

    // token情報を取得します
    // ここにEmailやUser IDなどが入っています
    // 特にuserの情報が必要ない場合はスルーです
    tokenInfo, err := service.Tokeninfo().AccessToken(tok.AccessToken).Context(context).Do()
    if err != nil {
        http.Error(w, err.Error(), http.StatusUnauthorized)
        return
    }

    // indexページにリダイレクトします
    // 6/11 update: ハードコードではなく定数を使用するようにした
    http.Redirect(w, r, "/", http.StatusMovedPermanently)
}

これでGoogle Accountが登録されているユーザであることは認証されました。
以上になります。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29