On App Engine using Go, correct method for using OAuth2

1.2k views Asked by At

I have been trying for the last few days to solve this, and having followed many of the examples and documentation out there without joy, I would like some assistance.

The following gives the Google authorization page, but then fails on returning to the handleOAuth2Callback page with the following error:

ERROR: Post https://accounts.google.com/o/oauth2/token: not an App Engine context

What am I doing wrong?

import (
    "google.golang.org/appengine"
    "google.golang.org/appengine/log"
    "google.golang.org/cloud"
    "golang.org/x/oauth2"
    "golang.org/x/oauth2/google"
    // "google.golang.org/api/drive/v2"
    "html/template"
    "net/http"
)

var cached_templates = template.Must(template.ParseGlob("templates/*.html"))

var conf = &oauth2.Config{
    ClientID:     "my client id",
    ClientSecret: "my client secret",
    RedirectURL:  "http://localhost:10080/oauth2callback",
    Scopes: []string{
        "https://www.googleapis.com/auth/drive",
        "https://www.googleapis.com/auth/userinfo.profile",
    },
    Endpoint: google.Endpoint,
}

func init() {
    http.HandleFunc("/", handleRoot)
    http.HandleFunc("/authorize", handleAuthorize)
    http.HandleFunc("/oauth2callback", handleOAuth2Callback)
}

func handleRoot(w http.ResponseWriter, r *http.Request) {
    err := cached_templates.ExecuteTemplate(w, "notAuthenticated.html", nil)
    if err != nil {
        http.Error(w, err.Error(), http.StatusNotFound)
    }
}

func handleAuthorize(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    url := conf.AuthCodeURL("")
    http.Redirect(w, r, url, http.StatusFound)
}

func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    hc := &http.Client{}
    ctx := cloud.NewContext(appengine.AppID(c), hc)
    log.Infof(c, "Ctx: %v", ctx)

    code := r.FormValue("code")
    log.Infof(c, "Code: %v", code)

    // Exchange the received code for a token
    tok, err := conf.Exchange(ctx, code)
    // tok, err := conf.Exchange(oauth2.NoContext, code)

    if err != nil {
        log.Errorf(c, "%v", err)
    }
    log.Infof(c, "Token: %v", tok)

    client := conf.Client(oauth2.NoContext, tok)
    log.Infof(c, "Client: %v", client)
}
1

There are 1 answers

0
Alistair Collins On

My current solution, including logging the logged in users display name and all the files in their Google Drive - please comment if you see any errors or improvements:

import (
    "golang.org/x/oauth2"
    "golang.org/x/oauth2/google"
    "google.golang.org/api/drive/v2"
    "google.golang.org/api/plus/v1"
    "google.golang.org/appengine"
    "google.golang.org/appengine/log"
    "html/template"

    "net/http"
)

var cached_templates = template.Must(template.ParseGlob("templates/*.html"))

var conf = &oauth2.Config{
    ClientID:     "my client id",       // Replace with correct ClientID
    ClientSecret: "my client secret",   // Replace with correct ClientSecret
    RedirectURL:  "http://localhost:10080/oauth2callback",
    Scopes: []string{
        "https://www.googleapis.com/auth/drive",
        "profile",
    },
    Endpoint: google.Endpoint,
}

func init() {
    http.HandleFunc("/", handleRoot)
    http.HandleFunc("/authorize", handleAuthorize)
    http.HandleFunc("/oauth2callback", handleOAuth2Callback)
}

func handleRoot(w http.ResponseWriter, r *http.Request) {
    err := cached_templates.ExecuteTemplate(w, "notAuthenticated.html", nil)
    if err != nil {
        http.Error(w, err.Error(), http.StatusNotFound)
    }
}

func handleAuthorize(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    url := conf.AuthCodeURL("")
    http.Redirect(w, r, url, http.StatusFound)
}

func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    code := r.FormValue("code")
    tok, err := conf.Exchange(c, code)
    if err != nil {
        log.Errorf(c, "%v", err)
    }
    client := conf.Client(c, tok)

    // PLUS SERVICE CLIENT
    pc, err := plus.New(client)
    if err != nil {
        log.Errorf(c, "An error occurred creating Plus client: %v", err)
    }
    person, err := pc.People.Get("me").Do()
    if err != nil {
        log.Errorf(c, "Person Error: %v", err)
    }
    log.Infof(c, "Name: %v", person.DisplayName)

    // DRIVE CLIENT
    dc, err := drive.New(client)
    if err != nil {
        log.Errorf(c, "An error occurred creating Drive client: %v", err)
    }
    files, err := dc.Files.List().Do()
    for _, value := range files.Items {
        log.Infof(c, "Files: %v", value.Title)
    }
}