HTTP-triggered Cloud Functions can be exposed to the public internet, but it’s often necessary to secure them and allow specific domains to call them.

Let’s look at an example. Imagine we have a simple HTTP-triggered Cloud Function that logs a request and returns a greeting.

# main.py
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def hello_world():
    print(f"Received request: {request.method} {request.path}")
    print(f"Headers: {request.headers}")
    if request.method == 'POST':
        print(f"Body: {request.get_data(as_text=True)}")
    return jsonify(message="Hello from Cloud Functions!")

# Example deployment command (replace with your actual project ID and region)
# gcloud functions deploy hello_world \
#   --runtime python310 \
#   --trigger-http \
#   --allow-unauthenticated \
#   --project YOUR_PROJECT_ID \
#   --region YOUR_REGION

Deploying this with --allow-unauthenticated makes it publicly accessible. Anyone can call its URL.

To secure this, we’ll use Identity Platform (or Firebase Authentication, which uses Identity Platform under the hood). This allows us to manage users and their authentication tokens.

First, enable Identity Platform in your Google Cloud project.

Next, we’ll modify the function to require authentication. Instead of --allow-unauthenticated, we’ll deploy without that flag, which defaults to requiring authentication.

# main.py (modified for auth)
from flask import Flask, request, jsonify, abort
from google.auth.transport import requests as google_requests
from google.oauth2 import id_token

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def hello_world():
    auth_header = request.headers.get('Authorization')
    if not auth_header or not auth_header.startswith('Bearer '):
        abort(401, description="Authorization header missing or malformed.")

    id_token_str = auth_header.split('Bearer ')[1]

    try:
        # Replace 'YOUR_PROJECT_ID' with your actual Google Cloud Project ID
        # This verifies the token's signature and issuer against your project's Identity Platform configuration.
        id_info = id_token.verify_oauth2_token(
            id_token_str,
            google_requests.Request(),
            audience='YOUR_PROJECT_ID.apps.googleusercontent.com' # This should match your Identity Platform client ID
        )
        print(f"Authenticated user: {id_info.get('email')}")
    except ValueError:
        # Token is invalid
        abort(401, description="Invalid authentication token.")

    print(f"Received request: {request.method} {request.path}")
    print(f"Headers: {request.headers}")
    if request.method == 'POST':
        print(f"Body: {request.get_data(as_text=True)}")
    return jsonify(message="Hello from authenticated Cloud Functions!")

# Example deployment command (replace with your actual project ID and region)
# gcloud functions deploy hello_world \
#   --runtime python310 \
#   --trigger-http \
#   --project YOUR_PROJECT_ID \
#   --region YOUR_REGION
#   # Note: --allow-unauthenticated is NOT used here.

To call this function, a client would need to obtain an Identity Platform ID token (e.g., after a user logs in) and include it in the Authorization: Bearer <ID_TOKEN> header.

Now, let’s consider Cross-Origin Resource Sharing (CORS). If your function is called from a web browser on a different domain than where the function is hosted (e.g., your-app.com calling your-function.cloudfunctions.net), the browser’s Same-Origin Policy will block it unless CORS is enabled.

Cloud Functions has built-in support for CORS. You can enable it during deployment or by modifying the function code.

To enable it via deployment:

gcloud functions deploy hello_world \
  --runtime python310 \
  --trigger-http \
  --allow-unauthenticated \
  --cors "https://your-app.com,https://another-domain.com" \
  --project YOUR_PROJECT_ID \
  --region YOUR_REGION

The --cors flag accepts a comma-separated list of allowed origins. You can also use * to allow all origins, but this is generally discouraged for production environments.

Alternatively, you can handle CORS within your function code using a library like flask-cors.

# main.py (modified for auth and CORS)
from flask import Flask, request, jsonify, abort
from flask_cors import CORS # Import CORS
from google.auth.transport import requests as google_requests
from google.oauth2 import id_token

app = Flask(__name__)
CORS(app, resources={r"/*": {"origins": "https://your-app.com"}}) # Configure CORS here

@app.route('/', methods=['GET', 'POST'])
def hello_world():
    auth_header = request.headers.get('Authorization')
    if not auth_header or not auth_header.startswith('Bearer '):
        abort(401, description="Authorization header missing or malformed.")

    id_token_str = auth_header.split('Bearer ')[1]

    try:
        # Replace 'YOUR_PROJECT_ID' with your actual Google Cloud Project ID
        id_info = id_token.verify_oauth2_token(
            id_token_str,
            google_requests.Request(),
            audience='YOUR_PROJECT_ID.apps.googleusercontent.com'
        )
        print(f"Authenticated user: {id_info.get('email')}")
    except ValueError:
        abort(401, description="Invalid authentication token.")

    print(f"Received request: {request.method} {request.path}")
    print(f"Headers: {request.headers}")
    if request.method == 'POST':
        print(f"Body: {request.get_data(as_text=True)}")
    return jsonify(message="Hello from authenticated and CORS-enabled Cloud Functions!")

# Example deployment command (replace with your actual project ID and region)
# gcloud functions deploy hello_world \
#   --runtime python310 \
#   --trigger-http \
#   --allow-unauthenticated \ # Still useful for testing, but remove for production with auth
#   --project YOUR_PROJECT_ID \
#   --region YOUR_REGION
#   # If using flask-cors, deployment command doesn't need --cors flag.

When you deploy the function with flask-cors configured, the function itself will set the appropriate Access-Control-Allow-Origin and other CORS headers in its response based on the configuration. The browser will then see these headers and allow the cross-origin request to proceed if the origin matches the allowed list.

The most surprising thing about securing HTTP-triggered Cloud Functions is that the audience parameter in id_token.verify_oauth2_token is not your function’s URL, but rather a specific identifier tied to your Identity Platform configuration, usually in the format YOUR_PROJECT_ID.apps.googleusercontent.com. This is crucial because it ensures the token was issued for your specific Google Cloud project’s authentication service, not a generic one.

When a client makes a request, the browser first performs a CORS preflight request (an OPTIONS request) if the actual request method is not simple (like POST, PUT, DELETE, or if custom headers are used). The Cloud Function, with CORS enabled, must respond to this preflight request with the correct Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers headers. Only after a successful preflight response will the browser send the actual request.

The next concept to explore is handling different HTTP methods and request bodies within your authenticated and CORS-enabled functions.

Want structured learning?

Take the full Cloud-functions course →