Reputation: 51
I have an app engine app behind endpoints and I'm having trouble following the documentation around adding authentication. My preference is to allow service accounts within the project through, then perform more granular app-side authorization.
In my case, most clients will be outside GCP and will be automated programs not people, so I'm using JSON key files thinking that is the way to go (correct me if I'm wrong please). I also don't want to have to redeploy the app to change user configs, so I follow the "GOOGLE ID JWT" information in the documentation from here:
https://cloud.google.com/endpoints/docs/openapi/service-to-service-auth
This is the security sections of my swagger JSON:
"securityDefinitions": {
"api_key": {
"type": "apiKey",
"name": "key",
"in": "query"
},
"google_id_token": {
"authorizationUrl": "",
"flow": "implicit",
"type": "oauth2",
"x-google-issuer": "https://accounts.google.com"
}
},
"security": [
{
"api_key": [],
"google_id_token": []
}
]
This deploys OK, but I am stumped on what to do from the client side to make use of the service account JSON.
In Go, I use the following, based on my understanding of the oauth2/google documentation:
package main
import (
"context"
"fmt"
"io/ioutil"
"log"
"os"
"golang.org/x/oauth2/google"
)
func main() {
ctx := context.Background()
apiKey := os.Getenv("API_KEY")
basePath := "https://SERVICE_NAME-dot-PROJECT_NAME.appspot.com" // changed for privacy
creds, _ := google.FindDefaultCredentials(ctx)
jwt, _ := google.JWTConfigFromJSON(creds.JSON, basePath)
client := jwt.Client(ctx)
res, _ := client.Get(fmt.Sprintf("%s/REQUEST_PATH?key=%s", basePath, apiKey)) // changed for privacy
body, _ := ioutil.ReadAll(res.Body)
log.Printf("### http status: %d %s", res.StatusCode, res.Status)
log.Printf("### http body: %s", body)
}
I've removed error-checking for this code paste for brevity, there are no errors when I run this. The result of this being:
2017/10/16 07:32:38 ### http status: 401 401 Unauthorized
2017/10/16 07:32:38 ### http body: {
"code": 16,
"message": "JWT validation failed: Missing or invalid credentials",
"details": [
{
"@type": "type.googleapis.com/google.rpc.DebugInfo",
"stackEntries": [],
"detail": "auth"
}
]
}
Upon inspection of the internals of what's happening, such as calling jwt.TokenSource(ctx).Token()
, I see that Google is returning an id_token
, but no access_token
(spotted by adding some debug logs to the jwt package):
2017/10/16 07:38:47 ### oauth2/jwt -> tokenURL: https://accounts.google.com/o/oauth2/token, form: url.Values{"grant_type":[]string{"urn:ietf:params:oauth:grant-type:jwt-bearer"}, "assertion":[]string{"...cut..."}}
2017/10/16 07:38:47 ### oauth2/jwt <- response: {
"id_token" : "...cut..."
}
2017/10/16 07:38:47 ### oauth2/jwt <- token: &oauth2.Token{AccessToken:"", TokenType:"", RefreshToken:"", Expiry:time.Time{wall:0x0, ext:63643740079, loc:(*time.Location)(0x1424160)}, raw:map[string]interface {}{"id_token":"...cut..."}}
So when the oauth2 HTTP transport tries to add the auth header -- which is tied to the AccessToken field above -- it results in a string like Authorization: Bearer
, thus the result fails.
I feel like I'm missing a step here which isn't obvious to me in the documentation.
Thanks for your time.
Upvotes: 1
Views: 1762
Reputation: 2595
It would be expected for the oauth client to return id_token
instead of access_token
. You're using JWTConfigFromJSON
, not ConfigFromJSON
, which is correct. I think the second argument to JWTConfigFromJSON
is incorrect, and you should be specifying scopes instead. In this case, try "email"
instead of basePath
.
Upvotes: 1