Secure Google Maps Static API (Web) With Signed Url (Python)

September 13, 2020

You can secure you API Key (Google Cloud Console -> Credentials)

  • Create separate API keys for Web/Android/iOS/Backend.
  • For web, you can secure it by Application restrictions using HTTP referrers, and fill in Website restrictions on list of websites allowed.
  • You can further secure API restrictions by specifying which API this key could access.

The things is restriction via HTTP referrers is not really secure, as people still could access it via empty or spoofed HTTP referrers.

To secure Google Maps Static API (Web) from abuse, you need to use signed url.

First thing we want to do is disable access from unsigned url.

  • Goto https://console.cloud.google.com/google/maps-apis/quotas, and select Maps Static API.
  • Expand Unsigned requests (if URL signing secret is defined), and set the quota to ZERO
    • Unsigned requests (if URL signing secret is defined) per day: 0
    • Unsigned requests (if URL signing secret is defined) per minute: 0
    • Unsigned requests (if URL signing secret is defined) per minute per user: 0

If you try to load static maps without signature/signed (e.g. https://maps.googleapis.com/maps/api/staticmap?center=35.01856,135.68077&markers=35.01856,135.68077&zoom=16&size=370x300&key=YOUR_KEY&sensor=false), it will show This site can't load Google Maps correctly.

Now, you need to get URL Signing Secret.

Sign your URL (Python 2.7 example)

import hashlib
import hmac
import base64
# import urllib.parse as urlparse
import urlparse

def sign_url(input_url, secret):
    if not input_url or not secret:
        raise Exception("Both input_url and secret are required")

    url = urlparse.urlparse(input_url)

    # We only need to sign the path+query part of the string
    url_to_sign = url.path + "?" + url.query

    # Decode the private key into its binary format
    # We need to decode the URL-encoded private key
    decoded_key = base64.urlsafe_b64decode(secret)

    # Create a signature using the private key and the URL-encoded
    # string using HMAC SHA1. This signature will be binary.
    signature = hmac.new(decoded_key, str.encode(url_to_sign), hashlib.sha1)

    # Encode the binary signature into base64 for use within a URL
    encoded_signature = base64.urlsafe_b64encode(signature.digest())

    original_url = url.scheme + "://" + url.netloc + url.path + "?" + url.query

    # Return signed URL
    return original_url + "&signature=" + encoded_signature.decode()
GOOGLE_API_KEY = 'AIzaSyD1...'
GOOGLE_MAPS_STATIC_URL_SIGNINING_SECRET = 'abc...'

url = f"https://maps.googleapis.com/maps/api/staticmap?center=35.01856,135.68077&markers=35.01856,135.68077&zoom=16&size=370x300&key={GOOGLE_API_KEY}&sensor=false"

# the final url will have a 'signature' parameter
url = signed_url(url, GOOGLE_MAPS_STATIC_URL_SIGNINING_SECRET)

References:

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.