Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a signed Google Maps API Geocoding request from a .NET Command Line Application

So I'm writing an application to cache geocoding data as I import records. I've got it working fine when I use an unsigned request, however I can't seem to figure out what's wrong when I try to use my company's clientid and signature. I always get a 403 Forbidden.

Here's my URL builder:

    private const string _googleUri = "http://maps.googleapis.com/maps/api/geocode/xml?address=";
    private const string _googleClientId = "XXXXXXXX";
    private const string _googleSignature = "XXXXXXXXXXXXXXXXXXXXXXXX";

//RESOLVED
    private static String GetGeocodeUri(string address)
    {
        ASCIIEncoding encoding = new ASCIIEncoding();
        string url = String.Format("{0}{1}&client={2}&sensor=false"
                                   , _googleUri
                                   , HttpUtility.UrlEncode(address)
                                   , _googleClientId);

        // converting key to bytes will throw an exception, need to replace '-' and '_' characters first.
        string usablePrivateKey = _googleSignature.Replace("-", "+").Replace("_", "/");
        byte[] privateKeyBytes = Convert.FromBase64String(usablePrivateKey);

        Uri uri = new Uri(url);
        byte[] encodedPathAndQueryBytes = encoding.GetBytes( uri.LocalPath + uri.Query );

        // compute the hash
        HMACSHA1 algorithm = new HMACSHA1(privateKeyBytes);
        byte[] hash = algorithm.ComputeHash(encodedPathAndQueryBytes);

        // convert the bytes to string and make url-safe by replacing '+' and '/' characters
        string signature = Convert.ToBase64String(hash).Replace("+", "-").Replace("/", "_");

        // Add the signature to the existing URI.
        return uri.Scheme + "://" + uri.Host + uri.LocalPath + uri.Query + "&signature=" + signature;

    } 

Here's the Program:

public static AddressClass GetResponseAddress(string address)
    {
        AddressClass GoogleAddress = new AddressClass();
        XmlDocument doc = new XmlDocument();
        String myUri = GetGeocodeUri(address);

        try
        {
            doc.Load(myUri);
            XmlNode root = doc.DocumentElement;
            if (root.SelectSingleNode("/GeocodeResponse/status").InnerText == "OK")
            {
                GoogleAddress.Latitude = Double.Parse(root.SelectSingleNode("/GeocodeResponse/result/geometry/location/lat").InnerText);
                GoogleAddress.Longitude = Double.Parse(root.SelectSingleNode("/GeocodeResponse/result/geometry/location/lat").InnerText);

            }
         }
         catch (Exception ex)
         {
            Console.WriteLine("Exception <" + ex.Message + ">");

         }           

        return GoogleAddress;
    }

Now, my initial reaction to it not working was that Google must be missing the referer domain because they must be registered. So I tried it with HttpWebRequest and set the referer to my domain, but still no dice.

//Not needed, Just an alternate method
public static AddressClass GetResponseAddress(string address)
    {
        AddressClass GoogleAddress = new AddressClass();
        WebClient client = new WebClient();
        XmlDocument doc = new XmlDocument();
        Uri myUri = new Uri(GetGeocodeUri(address));
        HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(myUri);
        myRequest.Referer = "http://www.myDomain.com/";

        //I've even tried pretending to be Chrome
        //myRequest.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7";

        try
        {
            doc.Load(myRequest.GetResponse().GetResponseStream());
            XmlNode root = doc.DocumentElement;
            if (root.SelectSingleNode("/GeocodeResponse/status").InnerText == "OK")
            {
                GoogleAddress.Latitude = Double.Parse(root.SelectSingleNode("/GeocodeResponse/result/geometry/location/lat").InnerText);
                GoogleAddress.Longitude = Double.Parse(root.SelectSingleNode("/GeocodeResponse/result/geometry/location/lat").InnerText);
            }
         }
         catch (Exception ex)
         {
              Console.WriteLine("Exception <" + ex.Message + ">");

         }

        return GoogleAddress;
    }

Any help would be much appreciated.

like image 773
copjon Avatar asked Jan 25 '12 17:01

copjon


People also ask

Can I use Google geocoding API for free?

The Geocoding API uses a pay-as-you-go pricing model. Geocoding API requests generate calls to one of two SKUs depending on the type of request: basic or advanced. Along with the overall Google Terms of Use, there are usage limits specific to the Geocoding API.

What is the signature Google Maps API?

The signing process uses an encryption algorithm to combine the URL and your shared secret. The resulting unique signature allows our servers to verify that any site generating requests using your API key is authorized to do so.

Can you geocode with Google Maps?

The Geocoding API is a service that provides geocoding and reverse geocoding of addresses. This service is also available as part of the client-side Google Maps JavaScript API, or for server-side use with the Java Client, Python Client, Go Client and Node. js Client for Google Maps Services.


2 Answers

const String gmeClientID = "gme-myClientId";
const String key = "myGoogleKey";

var urlRequest = String.Format("/maps/api/geocode/json?latlng={0},{1}&sensor=false&client={2}",Latitude,Longitude,gmeClientID);

HMACSHA1 myhmacsha1 = new HMACSHA1();
myhmacsha1.Key = Convert.FromBase64String(key); 
var hash = myhmacsha1.ComputeHash(Encoding.ASCII.GetBytes(urlRequest));

var url = String.Format("http://maps.googleapis.com{0}&signature={1}", urlRequest, Convert.ToBase64String(hash).Replace("+", "-").Replace("/", "_"));

var request = (HttpWebRequest)HttpWebRequest.Create(url);
like image 103
FlappySocks Avatar answered Sep 20 '22 02:09

FlappySocks


URL-encoding is sometimes necessary (see below) but not enough. Your problem is that you are not, in fact, signing your requests.

The value in your _googleSignature constant is not a signature, but your private cryptographic key, which is bad. Your private cryptographic key should never, ever be part of any request by itself.

Instead, you need to use it to generate a new signature for every unique request. Please see the Maps API for Business Authentication documentation, it also includes an example for Signing a URL in Java :)

When signing requests to the Google Maps API Web Services with your Maps API for Business client id and your private cryptographic key, the Referer header and source IP address are totally irrelevant ;)

URL-encoding is only necessary on the address parameter, as part of Building a Valid URL. You should never URL-encode your signature as it's already URL-safe by using the modified Base64 for URLs.

like image 39
miguev Avatar answered Sep 22 '22 02:09

miguev