Created
September 8, 2025 17:09
-
-
Save davidwtbuxton/a770ec47c654c730145162aeb6f0d271 to your computer and use it in GitHub Desktop.
App Engine website to test IAP auth for a service account
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| runtime: python312 | |
| automatic_scaling: | |
| max_instances: 2 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!doctype html> | |
| <html> | |
| <head> | |
| <title>Test</title> | |
| </head> | |
| <body> | |
| <h1>Test</h1> | |
| <h2>Website</h2> | |
| <form method="post"> | |
| <label>URL <input type="text" name="url"></label> | |
| <button type="submit">Go</button> | |
| </form> | |
| <h2>Response</h2> | |
| <code>{{ response }}</code> | |
| </body> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # requirements: pip install flask google-auth google-cloud-logging | |
| import logging | |
| import urllib.parse | |
| import flask | |
| import google.auth | |
| import google.cloud.logging | |
| import requests | |
| from google.auth.transport.requests import Request | |
| from google.oauth2 import id_token | |
| google.cloud.logging.Client().setup_logging(log_level=logging.DEBUG) | |
| logger = logging.getLogger(__name__) | |
| app = flask.Flask(__name__) | |
| def parse_iap_client_id(response): | |
| """The oauth client ID from an IAP auth flow. | |
| Or None if the response was not the start of IAP's authentication flow. | |
| """ | |
| # IAP redirects the user to sign in. The client ID is a separate query | |
| # parameter, but we could also extract it from the 'redirect_uri' param. | |
| # Oauth client IDs look like <proj-id>-<long-id>.apps.googleusercontent.com. | |
| if response.status_code == 302: | |
| location = response.headers["location"] | |
| parts = urllib.parse.urlparse(location) | |
| for param_name, value in urllib.parse.parse_qsl(parts.query): | |
| if param_name == "client_id": | |
| logger.debug("Found IAP redirect for client_id %r", value) | |
| return value | |
| def get_auth_token(client_id): | |
| """A bearer token (OIDC / identity) with the audience set to client_id. | |
| The email identity is the default App Engine identity (service account). | |
| """ | |
| return id_token.fetch_id_token(Request(), client_id) | |
| def fetch_url(url): | |
| """Fetch the URL, retrying if it requires IAP authentication.""" | |
| response = requests.get(url, allow_redirects=False) | |
| # Did we get redirected to IAP sign-in? | |
| if client_id := parse_iap_client_id(response): | |
| token = get_auth_token(client_id) | |
| headers = {"Authorization": f"Bearer {token}"} | |
| response = requests.get(url, allow_redirects=False, headers=headers) | |
| return response | |
| @app.route("/", methods=["GET", "POST"]) | |
| def home(): | |
| response = None | |
| url = flask.request.form.get("url") | |
| if url: | |
| response = fetch_url(url) | |
| return flask.render_template("home.html", response=response) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment