Skip to content

Commit e82c5bc

Browse files
liminwJon Wayne Parrott
authored and
Jon Wayne Parrott
committed
Add additional auth samples for endpoints.
Add three samples: 1. Use App Engine default service account client (without key file). 2. Use non-default service account client (without key file). 3. Use Google ID token client (without key file)
1 parent a18bc9d commit e82c5bc

File tree

10 files changed

+409
-8
lines changed

10 files changed

+409
-8
lines changed

appengine/flexible/endpoints/README.md

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@ With the API key, you can use the echo client to access the API:
5757

5858
$ python clients/echo-client.py https://YOUR-PROJECT-ID.appspot.com YOUR-API-KEY
5959

60-
### Using the JWT client.
60+
### Using the JWT client (with key file)
6161

62-
The JWT client demonstrates how to use service accounts to authenticate to endpoints. To use the client, you'll need both an API key (as described in the echo client section) and a service account. To create a service account:
62+
The JWT client demonstrates how to use a service account to authenticate to endpoints with the service account's private key file. To use the client, you'll need both an API key (as described in the echo client section) and a service account. To create a service account:
6363

6464
1. Open the Credentials page of the API Manager in the [Cloud Console](https://console.cloud.google.com/apis/credentials).
6565
2. Click 'Create credentials'.
@@ -76,7 +76,7 @@ Now you can use the JWT client to make requests to the API:
7676

7777
$ python clients/google-jwt-client.py https://YOUR-PROJECT-ID.appspot.com YOUR-API-KEY /path/to/service-account.json
7878

79-
### Using the ID Token client.
79+
### Using the ID Token client (with key file)
8080

8181
The ID Token client demonstrates how to use user credentials to authenticate to endpoints. To use the client, you'll need both an API key (as described in the echo client section) and a OAuth2 client ID. To create a client ID:
8282

@@ -93,3 +93,69 @@ To use the client ID for authentication:
9393
Now you can use the client ID to make requests to the API:
9494

9595
$ python clients/google-id-token-client.py https://YOUR-PROJECT-ID.appspot.com YOUR-API-KEY /path/to/client-id.json
96+
97+
### Using the App Engine default service account client (no key file needed)
98+
99+
The App Engine default service account client demonstrates how to use the Google App Engine default service account to authenticate to endpoints.
100+
We refer to the project that serves API requests as the server project. You also need to create a client project in the [Cloud Console](https://console.cloud.google.com). The client project is running Google App Engine standard application.
101+
102+
To use the App Engine default service account for authentication:
103+
104+
1. Update the `gae_default_service_account`'s `x-issuer` and `x-jwks_uri` in `swagger.yaml` with your client project ID.
105+
2. Redeploy your server application.
106+
3. Update clients/service_to_service_gae_default/main.py, replace 'YOUR-CLIENT-PROJECT-ID' and 'YOUR-SERVER-PROJECT-ID' with your client project ID and your server project ID.
107+
4. Upload your application to Google App Engine by invoking the following command. Note that you need to provide project ID in the command because there are two projects (server and client projects) here and gcloud needs to know which project to pick.
108+
109+
gcloud app deploy app.yaml --project=<YOUR-CLIENT-PROJECT-ID>
110+
111+
Your client app is now deployed at https://<YOUR-CLIENT-PROJECT-ID>.appspot.com. When you access https://<YOUR-CLIENT-PROJECT-ID>.appspot.com, your client calls your server project API using
112+
the client's service account.
113+
114+
### Using the service account client (no key file needed)
115+
116+
The service account client demonstrates how to use a non-default service account to authenticate to endpoints.
117+
We refer to the project that serves API requests as the server project. You also need to create a client project in the [Cloud Console](https://console.cloud.google.com).
118+
The client project is running Google App Engine standard application.
119+
120+
In the example, we use Google Cloud Identity and Access Management (IAM) API to create a JSON Web Token (JWT) for a service account, and use it to call an Endpoints API.
121+
122+
To use the client, you will need to enable "Service Account Actor" role for App Engine default service account:
123+
1. Go to [IAM page] of your client project (https://console.cloud.google.com/iam-admin/iam).
124+
2. For App Engine default service account, from “Role(s)” drop-down menu, select “Project”-“Service Account Actor”, and Save.
125+
126+
You also need to install Google API python library because the client code (main.py) uses googleapiclient,
127+
which is a python library that needs to be uploaded to App Engine with your application code. After you run "pip install -t lib -r requirements",
128+
Google API python client library should have already been installed under 'lib' directory. Additional information can be found
129+
[here](https://cloud.google.com/appengine/docs/python/tools/using-libraries-python-27#requesting_a_library).
130+
131+
To use the client for authentication:
132+
1. Update the `google_service_account`'s `x-issuer` and `x-jwks_uri` in `swagger.yaml` with your service account email.
133+
2. Redeploy your server application.
134+
3. Update clients/service_to_service_non_default/main.py, replace 'YOUR-SERVICE-ACCOUNT-EMAIL', 'YOUR-SERVER-PROJECT-ID' and 'YOUR-CLIENT-PROJECT-ID'
135+
with your service account email, your server project ID, and your client project ID.
136+
4. Upload your application to Google App Engine by invoking the following command. Note that you need to provide project ID in the command because there are two projects (server and client projects) here and gcloud needs to know which project to pick.
137+
138+
gcloud app deploy app.yaml --project=<YOUR-CLIENT-PROJECT-ID>
139+
140+
Your client app is now deployed at https://<YOUR-CLIENT-PROJECT-ID>.appspot.com. When you access https://<YOUR-CLIENT-PROJECT-ID>.appspot.com, your client calls your server project API using
141+
the client's service account.
142+
143+
### Using the ID token client (no key file needed)
144+
145+
This example demonstrates how to authenticate to endpoints from Google App Engine default service account using Google ID token.
146+
In the example, we first create a JSON Web Token (JWT) using the App Engine default service account. We then request a Google
147+
ID token using the JWT, and call an Endpoints API using the Google ID token.
148+
149+
We refer to the project that serves API requests as the server project. You also need to create a client project in the [Cloud Console](https://console.cloud.google.com).
150+
The client project is running Google App Engine standard application.
151+
152+
To use the client for authentication:
153+
1. Update the `google_id_token`'s audiences, replace `YOUR-SERVER-PROJECT-ID` with your server project ID.
154+
2. Redeploy your server application.
155+
3. Update clients/service_to_service_google_id_token/main.py, replace 'YOUR-CLIENT-PROJECT-ID' and 'YOUR-SERVER-PROJECT-ID' with your client project ID and your server project ID.
156+
4. Upload your application to Google App Engine by invoking the following command. Note that you need to provide project ID in the command because there are two projects (server and client projects) here and gcloud needs to know which project to pick.
157+
158+
gcloud app deploy app.yaml --project=<YOUR-CLIENT-PROJECT-ID>
159+
160+
Your client app is now deployed at https://<YOUR-CLIENT-PROJECT-ID>.appspot.com. When you access https://<YOUR-CLIENT-PROJECT-ID>.appspot.com, your client calls your server project API from
161+
the client's service account using Google ID token.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
runtime: python27
2+
api_version: 1
3+
threadsafe: true
4+
5+
handlers:
6+
- url: /.*
7+
script: main.app
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Copyright 2016 Google Inc. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0(the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http: // www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Example of calling a Google Cloud Endpoint API with a JWT signed by
16+
Google App Engine Default Service Account."""
17+
18+
import base64
19+
import httplib
20+
import json
21+
import time
22+
23+
from google.appengine.api import app_identity
24+
import webapp2
25+
26+
DEFAUTL_SERVICE_ACCOUNT = '[email protected]'
27+
HOST = "YOUR-SERVER-PROJECT-ID.appspot.com"
28+
29+
30+
def generate_jwt():
31+
"""Generates a signed JSON Web Token using the Google App Engine default
32+
service account."""
33+
now = int(time.time())
34+
35+
header_json = json.dumps({
36+
"typ": "JWT",
37+
"alg": "RS256"})
38+
39+
payload_json = json.dumps({
40+
'iat': now,
41+
# expires after one hour.
42+
"exp": now + 3600,
43+
# iss is the Google App Engine default service account email.
44+
'iss': DEFAUTL_SERVICE_ACCOUNT,
45+
'sub': DEFAUTL_SERVICE_ACCOUNT,
46+
# aud must match 'audience' in the security configuration in your
47+
# swagger spec.It can be any string.
48+
'aud': 'echo.endpoints.sample.google.com',
49+
"email": DEFAUTL_SERVICE_ACCOUNT
50+
})
51+
52+
headerAndPayload = '{}.{}'.format(
53+
base64.urlsafe_b64encode(header_json),
54+
base64.urlsafe_b64encode(payload_json))
55+
(key_name, signature) = app_identity.sign_blob(headerAndPayload)
56+
signed_jwt = '{}.{}'.format(
57+
headerAndPayload,
58+
base64.urlsafe_b64encode(signature))
59+
60+
return signed_jwt
61+
62+
63+
def make_request(signed_jwt):
64+
"""Makes a request to the auth info endpoint for Google JWTs."""
65+
headers = {'Authorization': 'Bearer {}'.format(signed_jwt)}
66+
conn = httplib.HTTPSConnection(HOST)
67+
conn.request("GET", '/auth/info/googlejwt', None, headers)
68+
res = conn.getresponse()
69+
conn.close()
70+
return res.read()
71+
72+
73+
class MainPage(webapp2.RequestHandler):
74+
def get(self):
75+
self.response.headers['Content-Type'] = 'text/plain'
76+
signed_jwt = generate_jwt()
77+
res = make_request(signed_jwt)
78+
self.response.write(res)
79+
80+
81+
app = webapp2.WSGIApplication([
82+
('/', MainPage),
83+
], debug=True)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
runtime: python27
2+
api_version: 1
3+
threadsafe: true
4+
5+
handlers:
6+
- url: /.*
7+
script: main.app
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Copyright 2016 Google Inc. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0(the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http: // www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Example of calling a Google Cloud Endpoint API from Google App Engine
16+
Default Service Account using Google ID token."""
17+
18+
import base64
19+
import httplib
20+
import json
21+
import time
22+
import urllib
23+
24+
from google.appengine.api import app_identity
25+
import webapp2
26+
27+
DEFAUTL_SERVICE_ACCOUNT = "[email protected]"
28+
HOST = "YOUR-SERVER-PROJECT-ID.appspot.com"
29+
TARGET_AUD = "[email protected]"
30+
31+
32+
def generate_jwt():
33+
"""Generates a signed JSON Web Token using the Google App Engine default
34+
service account."""
35+
now = int(time.time())
36+
37+
header_json = json.dumps({
38+
"typ": "JWT",
39+
"alg": "RS256"})
40+
41+
payload_json = json.dumps({
42+
"iat": now,
43+
# expires after one hour.
44+
"exp": now + 3600,
45+
# iss is the Google App Engine default service account email.
46+
"iss": DEFAUTL_SERVICE_ACCOUNT,
47+
# scope must match 'audience' for google_id_token in the security
48+
# configuration in your swagger spec.
49+
"scope": TARGET_AUD,
50+
# aud must be Google token endpoints URL.
51+
"aud": "https://www.googleapis.com/oauth2/v4/token"
52+
})
53+
54+
headerAndPayload = '{}.{}'.format(
55+
base64.urlsafe_b64encode(header_json),
56+
base64.urlsafe_b64encode(payload_json))
57+
(key_name, signature) = app_identity.sign_blob(headerAndPayload)
58+
signed_jwt = '{}.{}'.format(
59+
headerAndPayload,
60+
base64.urlsafe_b64encode(signature))
61+
62+
return signed_jwt
63+
64+
65+
def get_id_token():
66+
"""Request a Google ID token using a JWT."""
67+
params = urllib.urlencode({
68+
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
69+
'assertion': generate_jwt()})
70+
headers = {"Content-Type": "application/x-www-form-urlencoded"}
71+
conn = httplib.HTTPSConnection("www.googleapis.com")
72+
conn.request("POST", "/oauth2/v4/token", params, headers)
73+
res = json.loads(conn.getresponse().read())
74+
conn.close()
75+
return res['id_token']
76+
77+
78+
def make_request(token):
79+
"""Makes a request to the auth info endpoint for Google ID token."""
80+
headers = {'Authorization': 'Bearer {}'.format(token)}
81+
conn = httplib.HTTPSConnection(HOST)
82+
conn.request("GET", '/auth/info/googleidtoken', None, headers)
83+
res = conn.getresponse()
84+
conn.close()
85+
return res.read()
86+
87+
88+
class MainPage(webapp2.RequestHandler):
89+
def get(self):
90+
self.response.headers['Content-Type'] = 'text/plain'
91+
token = get_id_token()
92+
res = make_request(token)
93+
self.response.write(res)
94+
95+
96+
app = webapp2.WSGIApplication([
97+
('/', MainPage),
98+
], debug=True)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
runtime: python27
2+
api_version: 1
3+
threadsafe: true
4+
5+
handlers:
6+
- url: /.*
7+
script: main.app
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from google.appengine.ext import vendor
2+
3+
vendor.add('lib')
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Copyright 2016 Google Inc. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0(the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http: // www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Example of calling a Google Cloud Endpoint API with a JWT signed by a
16+
Service Account."""
17+
18+
import base64
19+
import httplib
20+
import json
21+
import time
22+
23+
from googleapiclient.discovery import build
24+
import httplib2
25+
from oauth2client.contrib.appengine import AppAssertionCredentials
26+
import webapp2
27+
28+
SERVICE_ACCOUNT_EMAIL = "YOUR-SERVICE-ACCOUNT-EMAIL"
29+
HOST = "YOUR-SERVER-PROJECT-ID.appspot.com"
30+
SERVICE_ACCOUNT = \
31+
"projects/YOUR-CLIENT-PROJECT-ID/serviceAccounts/YOUR-SERVICE-ACCOUNT-EMAIL"
32+
33+
34+
def generate_jwt():
35+
"""Generates a signed JSON Web Token using a service account."""
36+
credentials = AppAssertionCredentials(
37+
'https://www.googleapis.com/auth/iam')
38+
http_auth = credentials.authorize(httplib2.Http())
39+
service = build(serviceName='iam', version='v1', http=http_auth)
40+
41+
now = int(time.time())
42+
43+
header_json = json.dumps({
44+
"typ": "JWT",
45+
"alg": "RS256"})
46+
47+
payload_json = json.dumps({
48+
'iat': now,
49+
# expires after one hour.
50+
"exp": now + 3600,
51+
# iss is the service account email.
52+
'iss': SERVICE_ACCOUNT_EMAIL,
53+
'sub': SERVICE_ACCOUNT_EMAIL,
54+
# aud must match 'audience' in the security configuration in your
55+
# swagger spec.It can be any string.
56+
'aud': 'echo.endpoints.sample.google.com',
57+
"email": SERVICE_ACCOUNT_EMAIL
58+
})
59+
60+
headerAndPayload = '{}.{}'.format(
61+
base64.urlsafe_b64encode(header_json),
62+
base64.urlsafe_b64encode(payload_json))
63+
slist = service.projects().serviceAccounts().signBlob(
64+
name=SERVICE_ACCOUNT,
65+
body={'bytesToSign': base64.b64encode(headerAndPayload)})
66+
res = slist.execute()
67+
signature = base64.urlsafe_b64encode(
68+
base64.decodestring(res['signature']))
69+
signed_jwt = '{}.{}'.format(headerAndPayload, signature)
70+
71+
return signed_jwt
72+
73+
74+
def make_request(signed_jwt):
75+
"""Makes a request to the auth info endpoint for Google JWTs."""
76+
headers = {'Authorization': 'Bearer {}'.format(signed_jwt)}
77+
conn = httplib.HTTPSConnection(HOST)
78+
conn.request("GET", '/auth/info/googlejwt', None, headers)
79+
res = conn.getresponse()
80+
conn.close()
81+
return res.read()
82+
83+
84+
class MainPage(webapp2.RequestHandler):
85+
def get(self):
86+
self.response.headers['Content-Type'] = 'text/plain'
87+
signed_jwt = generate_jwt()
88+
res = make_request(signed_jwt)
89+
self.response.write(res)
90+
91+
92+
app = webapp2.WSGIApplication([
93+
('/', MainPage),
94+
], debug=True)

0 commit comments

Comments
 (0)