Golang App-engine OAuth Authorization - google-app-engine

I am implementing an app-engine webapp written in Go that needs user authentication and authorization to access some user data on Google's APIs.
Do I still need to use the goauth2 and gorilla packages to implement authorization or is there some functionality in the "appengine" and "appengine/user" packages that implements Oauthorization natively for app-engine apps? I do not mind going ahead with goauth2, but if there is a better way...

You can use the "appengine/user" package for authentication with OAuth but I'm a fraid that you must implement authorization yourself.
For OAuth authentication with "appengine/user" package, see https://developers.google.com/appengine/docs/go/oauth/#Go_OAuth_and_App_Engine

Here's an example from the OAuth for Go Overview on the Google App Engine Docs site:
import (
"fmt"
"net/http"
"appengine"
"appengine/user"
)
func welcome(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
u, err := user.CurrentOAuth(c, "")
if err != nil {
http.Error(w, "OAuth Authorization header required", http.StatusUnauthorized)
return
}
if !u.Admin {
http.Error(w, "Admin login only", http.StatusUnauthorized)
return
}
fmt.Fprintf(w, `Welcome, admin user %s!`, u)
}
It looks like appengine/user has the function user.CurrentOAuth() to provide authentication functionality.
For authorization, there's an example using OpenID Here.
There is a caveat; The documentation states:
Note that using OAuth to identify your users is completely orthogonal to the standard user authentication modes. For example, pages marked with login: required or login: admin will refuse to load if the user is only authenticated via OAuth.
Full Go GAE reference available Here.

Related

Go client to access GAE login required apps

I wanted to authenticate myself (Google Account) using a golang client against protected apps on Google App Engine where login: required or login: admin is specified in app.yaml.
First I wrote a simple OAuth2 offline access client but it didn't work at all - the server just redirects clients to Google Account's sign in page. I've tried with various Google API scopes and currently no luck.
package main
import (
"context"
"fmt"
"io"
"log"
"os"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
const (
AppURL = "https://login-requried-app.appspot.com"
AuthClientID = "....."
AuthClientSecret = "....."
AuthRedirectURL = "urn:ietf:wg:oauth:2.0:oob"
AuthScope = "https://www.googleapis.com/auth/cloud-platform"
)
func main() {
ctx := context.Background()
conf := &oauth2.Config{
ClientID: AuthClientID,
ClientSecret: AuthClientSecret,
Endpoint: google.Endpoint,
RedirectURL: AuthRedirectURL,
Scopes: []string{AuthScope},
}
url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline)
fmt.Printf("Visit the URL for the auth dialog: %v\n", url)
fmt.Printf("Enter authentication code: ")
var code string
if _, err := fmt.Scan(&code); err != nil {
log.Fatal(err)
}
tok, err := conf.Exchange(ctx, code)
if err != nil {
log.Fatal(err)
}
client := conf.Client(ctx, tok)
res, err := client.Get(AppURL)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
log.Println(res.Status)
io.Copy(os.Stdout, res.Body)
}
I'm looking for the details of GAE's user authentication modes used in such apps to write a non web browser client. I feel it's something different than standard OAuth2 authentication/authorization after reading App Engine Users API docs and code that is receiving user information via HTTP headers like X-AppEngine-User-Email.
Users Go API Overview
google.golang.org/appengine/user package document
user.Current() implementation (it's for flex env but almost the same in standard env)
UPDATE: After some research it looks like the GAE frontend uses SACSID cookie for tracking authenticated sessions, which isn't related to the OAuth2 standard. Indeed as stated in Users Go API document:
Note that using OAuth to identify your users is completely orthogonal to the standard user authentication modes. For example, pages marked with login: required or login: admin will refuse to load if the user is only authenticated via OAuth.
Is there any supported way for a CLI application to acquire SACSID properly authorized by user's consent?
Related questions:
Access an App Engine app from command line using OAuth2?
On Google App Engine, can I relate a Google OAuth 2 Token and a SACSID token I got using Android's AccountManager?
Given the situation you explain here, I suggest using a remote API. This way you can access App Engine services from your Go app.
First you have to configure your app.yaml file by adding the following:
- url: /_ah/remote_api
script: _go_app
You also have to add the following import to your .go source file:
import _ "google.golang.org/appengine/remote_api"
When this is done, deploy your updated app to App Engine:
gcloud app deploy app.yaml
The website I included here includes an example on how to use the remote API. You can try it and adapt your code if this works for you.

How can I give my Google Cloud App Engine access to my Firebase Storage bucket

I'm getting an error: API error 7 (images: ACCESS_DENIED)
Normally I would have the Firebase app associated with my Google App Engine service, but since Firestore is not compatible with App Engine, I have to create seperate instances of the service. However, now I need my app engine image service to be able to reference the files in the firebase cloud storage folder. How do I set up access, would I need to create some sort of IAM that's shared across both services?
Example of my app-engine go function referencing the firebase storage bucket the-other-firebase-app
func photoHandler(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
v := r.URL.Query()
fileName := v.Get("file")
if (fileName == "") {
fmt.Fprint(w, "file name is required")
} else {
filename := "/gs/the-other-firebase-app.appspot.com/photos/" + fileName;
blobKey,err := blobstore.BlobKeyForFile(c, filename)
if (err == nil) {
url, urlErr := image.ServingURL(c, blobKey, nil)
if (urlErr == nil) {
fmt.Fprint(w, url)
} else {
fmt.Fprint(w, urlErr)
}
} else {
fmt.Fprint(w, "image does not exist")
}
}
}
I had a very similar issue, but the exact error message thrown was:
_"your-project-name#appspot.gserviceaccount.com does not have storage.objects.get access to storage_file_path_here>"_
I fixed it thanks to suggestions from eltonnobrega on Github: https://github.com/firebase/functions-samples/issues/299
Here's a summary:
Got to Google Cloud Console and select the project which your storage bucket is under - the bucket you want to access from your service account.
Go to the Storage tab
On the browser tab, you should see the storage buckets in this project. Each one will have an options icon at the line ending on the right (three vertical dots)
From these options choose Edit Bucket permissions
From there you can add members. Add your service account (your-project-name#appspot.gserviceaccount.com) and give it the Storage Object Admin role
If your app is a standard env one it should already have a service account, see Using service accounts:
The App Engine application runs using the identity of the App Engine
default service account. You can see this account on the IAM page in
the Cloud Platform Console.
If your app is a flex engine one see Service Account for the App Engine Flexible Environment:
When you enable the Google App Engine flexible environment API, a
specific service account is automatically created. You can view your
project's service accounts in the IAM section of the Cloud Platform
Console. The email for the App Engine flexible environment service
account is
service-[PROJECT-NUMBER]#gae-api-prod.google.com.iam.gserviceaccount.com,
where [PROJECT-NUMBER] is the project number listed in the IAM
settings.
In the Firebase app project you need to add the GAE app's service account as a team member and grant it the necessary permissions.
As mentioned in the comment:
Firebase Dasbhoard -> Project Settings Cog -> Users and permissions –
MonkeyBonkey
Potentially of interest could also be Integrate with Google Cloud Platform.

Go, Appengine, SMTP, Gmail

For some reason I cannot figure out how to send emails using a gmail account, Appengine and Golang.
Here's what I've done:
I went to Google Cloud Platform > Appengine > Settings > Select Project and I added the gmail account on Email API authorized senders.
I' tried to make this work using the code from (https://golang.org/pkg/net/smtp/#pkg-examples) (func SendMail)
package main
import (
"log"
"net/smtp"
)
func main() {
// Set up authentication information.
auth := smtp.PlainAuth("", "user#gmail.com", "password", "smtp.gmail.com")
// Connect to the server, authenticate, set the sender and recipient,
// and send the email all in one step.
to := []string{"recipient#example.net"}
msg := []byte("To: recipient#example.net\r\n" +
"Subject: discount Gophers!\r\n" +
"\r\n" +
"This is the email body.\r\n")
err := smtp.SendMail(smtp.gmail.com:587", auth, "sender#example.org", to, msg)
if err != nil {
log.Fatal(err)
}
}
On the front-end (JavaScript) I get an unsuccessful response after trying to run this code.
I've been running this on the appengine staging server
I tried different smtp server, ports, users and it still not work (support.google.com/a/answer/176600?hl=en)
I found a few examples on github and some other blog and I tried them but it didn't make different.
github.com/golang/go/wiki/SendingMail
nathanleclaire.com/blog/2013/12/17/sending-email-from-gmail-using-golang/
On all the examples it everything looks straight forward but there's something that I'm definitely missing or misunderstanding.
There're some limitations with establishing raw tcp connections on GAE:
https://cloud.google.com/appengine/docs/go/sockets/#limitations_and_restrictions
I would recommend to use GAE mail api (which is slightly diffrent from standart smtp package) to send emails:
https://cloud.google.com/appengine/docs/go/mail/sending-receiving-with-mail-api

Go GAE Using LoginURLFederated function returns API error 2 (user: NOT_ALLOWED)

I am trying to use LoginURLFederated for logging users in. My code is as follows:-
c := appengine.NewContext(r)
u, _ := user.CurrentOAuth(c, "")
if u == nil {
if loginUrl, err := user.LoginURLFederated(c, "/webservice/uid-test", "gmail.com"); err != nil {
http.Error(w, "here1"+err.Error(), http.StatusInternalServerError)
return
} else {
http.Redirect(w, r, loginUrl, http.StatusFound)
return
}
}
I got this error
here1API error 2 (user: NOT_ALLOWED)
When I change from LoginURLFederated to LoginURL function, it worked fine. What did I miss? I tried to use user.Current(c) as well, however, I didn't work either.
The reason I am doing this is because I am trying to verify the idToken that is being sent from an android app that uses google sign-in api for signing in. Google sign in api implements oauth2 standard and generate idToken for other app: in this case, my web app written in Go running on GAE, to use the idToken to verify the signed in user. More information about android google sign in is here https://developers.google.com/identity/sign-in/android/backend-auth#send-the-id-token-to-your-server
However, I didn't realize that Google ID that I received from calling user.LoginURL(c) in Go web app on GAE was different from the sub value (ID) received from calling Google idToken endpoint here
https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=value
Then I realized that the IDs were generated by two different Google endpoints. I decided to use Google federated login function on the web app side to match the ID received from
https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=value
since federated login implements openid (oauth) but I am receiving an unknown error API error 2 (user: NOT_ALLOWED)
Am I doing things right? Can federated login generate the identical id with the sub value (id) sent from google idToken endpoint?

Go App engine oauth2 request

I've been trying to implement OAuth2 for Go with App engine server to server request.
Here is the code (following the example):
oauthConf, err := google.NewServiceAccountJSONConfig(
"./key.json",
"https://www.googleapis.com/auth/adsense.readonly",
)
if err != nil {
log.Fatal(err)
}
client := http.Client{Transport: oauthConf.NewTransport()}
resp, err := client.Get(urlStr)
...
And I get the error message:
http.DefaultTransport and http.DefaultClient are not available in App Engine.
I'm pretty sure the json.key file is valid as for the other stuff
Googling lead me to understand that it is preferred to use urlfetch, but I can't figure out how to make the setup with the oauth2 config.
The NewServiceAccountJSONConfig returns a oauth2.JWTConfig
https://github.com/golang/oauth2/blob/master/google/google.go#L69-L87
Its .NewTransport() you are using:
https://github.com/golang/oauth2/blob/master/jwt.go#L86-L88
Defaults to the http.DefaultTransport, that is not supported on appengine:
https://github.com/golang/oauth2/blob/master/jwt.go#L163-L168
I would go for using the AppEnigneConfig instead (if possible). See http://godoc.org/github.com/golang/oauth2/google
c := appengine.NewContext(nil)
config := google.NewAppEngineConfig(c, "https://www.googleapis.com/auth/bigquery")
// The following client will be authorized by the App Engine
// app's service account for the provided scopes.
client := http.Client{Transport: config.NewTransport()}
client.Get("...")
Otherwise if you need to use ServiceAccountJSONConfig, you probably can, but you'll need to use urlfetch Client and Transport. Have a look how AppEngineConfig is set up: https://github.com/golang/oauth2/blob/master/google/appengine.go

Resources