One of the authentication protocol that is supported by most of Google Cloud services is OpenID Connect (OIDC). For instance, you can secure communication between a Cloud Task and HTTP-triggred Cloud Function using it quite easily. You just need to pass service account email when submitting Cloud Task job and make sure you keep the default authentication for HTTP Cloud Function. You can also expect similar setup when using Cloud Scheduler and HTTP-triggered Cloud Run: the triggerring service will automatically generate OIDC token while the triggered service will automatically validate them ?
Now the catch is more flexible services like App Engine and Compute Engine don’t have these feature in built. It can be understood as implementing such feature may reduce their flexibility. So, for example, if you want to use OIDC to secure communication between App Engine services, you will need to write few codes to do it manually.
This one of the method that I use to manually generate Google OIDC token for default service account using Python 3.x and google-auth==1.20.0
module:
import google.oauth2.id_token import google.auth.transport.requests google_request = google.auth.transport.requests.Request() url = "https://myproject.appspot.com/myservice" token = google.oauth2.id_token.fetch_id_token(google_request, url)
Once we get the token, we can make a secure HTTP request by including that token in Authorization header {"Authorization": token}
.
If you want to test this script locally, make sure you export GOOGLE_APPLICATION_CREDENTIALS environment variable that contains absolute path to your service account JSON credential
Finally, this is the script I use to validate the OIDC token inside djangorestframework==3.11.0
request. I found that this script is able to validate both the token generated using script above and the one generated automatically by Cloud Task.
from rest_framework.request import Request from google.oauth2 import id_token from google.auth.transport import requests def auth_request_oidc(request: Request) -> dict: try: token = request.headers["Authorization"].split(" ").pop() return id_token.verify_oauth2_token(token, requests.Request()) except Exception as e: logger.exception(e) raise AuthenticationFailed()
The id_token.verify_oauth2_token()
returns the JWT token payload decribed in the documentation. I think the fields/claims that we may want to validate are iss
(issuer), email
and email_verified
. But I’m not yet experienced in JWT, so I’m not yet sure whether it is possible to forge these fields/claims. But at least, I don’t let my APIs with zero security. Right? ?
Cheers! ?