Skip to content

Commit

Permalink
Adding 2 python scripts to generate JWT tokens based on https://endpo…
Browse files Browse the repository at this point in the history
  • Loading branch information
philippQubit committed Jul 14, 2017
1 parent 371c201 commit a0c0499
Show file tree
Hide file tree
Showing 3 changed files with 261 additions and 0 deletions.
98 changes: 98 additions & 0 deletions auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Generate custom JWT for service-to-service authentication
There are two forms of JSON Web Token (JWT) signed by Google: A Google ID token or a custom JWT that
is signed only by the service account of the caller. The following two endpoint security definitions
can be used:
```
securityDefinitions:
[ISSUER_NAME]:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
x-google-issuer: "[YOUR-SERVICE-ACCOUNT-EMAIL]"
x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/[YOUR-SERVICE-ACCOUNT-EMAIL]"
```
```
securityDefinitions:
google_id_token:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
x-google-issuer: "https://accounts.google.com"
```
This is based on the following endpoint documentation (https://cloud.google.com/endpoints/docs/service-to-service-auth).


## Setup
Before using the script please run the following command to install python dependences:
```
$pip install google-cloud
```
You will also need to create and save the service account json file.


## Usage
To generate a custom JWT that is signed only by the service account use the following:
```
$ python generate-jwt.py -h
usage: generate-jwt.py [-h] [-e EMAIL] [-g GROUPID] [-iss ISSUER]
aud service_account_file
Python script generates a signed JWT token based on the input payload
positional arguments:
aud Audience . This must match 'audience' in the security
configuration in the swagger spec. It can be any
string
service_account_file The path to your service account json file.
optional arguments:
-h, --help show this help message and exit
-e EMAIL, --email EMAIL
Email claim in JWT
-g GROUPID, --groupId GROUPID
GroupId claim in JWT
-iss ISSUER, --issuer ISSUER
Issuer claim. This will also be used for sub claim
```
To generate a Google ID token JWT use the following:
```
$ python generate-google-id-jwt.py -h
usage: generate-google-id-jwt.py [-h] [-iss ISSUER]
aud service_account_file
Python script generates a signed Google ID JWT token based on the input payload
positional arguments:
aud Audience . This must match 'audience' in the security
configuration in the swagger spec. It can be any
string
service_account_file The path to your service account json file.
optional arguments:
-h, --help show this help message and exit
-iss ISSUER, --issuer ISSUER
Issuer claim. This will also be used for sub claim
```


## Examples
1. Generate JWT token without any custom claims
```
$ python generate-jwt.py <YOUR-AUDIENCE> /path/to/service_account.json
```
2. Generate a Google ID JWT token without any custom claims
```
$ python generate-google-id-jwt.py <YOUR-AUDIENCE> /path/to/service_account.json
```
3. Generate JWT token with email claim
```
$ python generate-jwt.py -e [email protected] <YOUR-AUDIENCE> /path/to/service_account.json
```
4. Generate JWT token with groupId claim
```
$ python generate-jwt.py -g acme <YOUR-AUDIENCE> /path/to/service_account.json
```
5. Generate JWT token with both email and groupId claim
```
$ python generate-jwt.py -e [email protected] -g acme <YOUR-AUDIENCE> /path/to/service_account.json
```
92 changes: 92 additions & 0 deletions auth/generate-google-id-jwt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env python
# Copyright 2017 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Python script generates a Google ID token based on the input payload"""

import base64
import httplib
import json
import argparse
import time
import urllib

import oauth2client.crypt
import googleapiclient.discovery
from oauth2client.service_account import ServiceAccountCredentials
from oauth2client.client import GoogleCredentials

def generate_jwt(args):
"""Generates a signed JSON Web Token using a service account. Based on https://cloud.google.com/endpoints/docs/service-to-service-auth"""
# Make sure the service account has "Service Account Token Creator" permissions in Google IAM
credentials = ServiceAccountCredentials.from_json_keyfile_name(
args.service_account_file).create_scoped(['https://www.googleapis.com/auth/cloud-platform'])

service = googleapiclient.discovery.build(
serviceName='iam', version='v1', credentials=credentials)

now = int(time.time())
header_json = json.dumps({
"typ": "JWT",
"alg": "RS256"})

payload_json = json.dumps({
'iat': now,
"exp": now + 3600,
'iss': args.issuer if args.issuer else credentials.service_account_email,
"target_audience": 'https://' + args.aud,
"aud": "https://www.googleapis.com/oauth2/v4/token"
})

header_and_payload = '{}.{}'.format(
base64.urlsafe_b64encode(header_json),
base64.urlsafe_b64encode(payload_json))
slist = service.projects().serviceAccounts().signBlob(
name="projects/-/serviceAccounts/" + credentials.service_account_email,
body={'bytesToSign': base64.b64encode(header_and_payload)})
res = slist.execute()
signature = base64.urlsafe_b64encode(
base64.decodestring(res['signature']))
signed_jwt = '{}.{}'.format(header_and_payload, signature)

return signed_jwt

def main(args):
"""Request a Google ID token using a JWT."""
params = urllib.urlencode({
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion': generate_jwt(args)})
headers = {"Content-Type": "application/x-www-form-urlencoded"}
conn = httplib.HTTPSConnection("www.googleapis.com")
conn.request("POST", "/oauth2/v4/token", params, headers)
res = json.loads(conn.getresponse().read())
conn.close()
return res['id_token']

if __name__ == '__main__':
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
# positional arguments
parser.add_argument(
"aud",
help="Audience . This must match 'audience' in the security configuration"
" in the swagger spec. It can be any string")
parser.add_argument(
'service_account_file',
help='The path to your service account json file.')

#optional arguments
parser.add_argument("-iss", "--issuer", help="Issuer claim. This will also be used for sub claim")
print main(parser.parse_args())
71 changes: 71 additions & 0 deletions auth/generate-jwt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env python
# Copyright 2017 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Python script generates a signed JWT token based on the input payload"""

import argparse
import time

import oauth2client.crypt
from oauth2client.service_account import ServiceAccountCredentials

def main(args):
"""Generates a signed JSON Web Token using a Google API Service Account."""
credentials = ServiceAccountCredentials.from_json_keyfile_name(
args.service_account_file)

now = int(time.time())

payload = {
"exp": now + credentials.MAX_TOKEN_LIFETIME_SECS,
"iat": now,
"aud": args.aud,
}

if args.email:
payload["email"] = args.email
if args.groupId:
payload["groupId"] = args.groupId

if args.issuer:
payload["iss"] = args.issuer
payload["sub"] = args.issuer
else:
payload["iss"] = credentials.service_account_email
payload["sub"] = credentials.service_account_email

signed_jwt = oauth2client.crypt.make_signed_jwt(
credentials._signer, payload, key_id=credentials._private_key_id)

return signed_jwt

if __name__ == '__main__':
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
# positional arguments
parser.add_argument(
"aud",
help="Audience . This must match 'audience' in the security configuration"
" in the swagger spec. It can be any string")
parser.add_argument(
'service_account_file',
help='The path to your service account json file.')

#optional arguments
parser.add_argument("-e", "--email", help="Email claim in JWT")
parser.add_argument("-g", "--groupId", help="GroupId claim in JWT")
parser.add_argument("-iss", "--issuer", help="Issuer claim. This will also be used for sub claim")
print main(parser.parse_args())

0 comments on commit a0c0499

Please sign in to comment.