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.
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.
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.
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.
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);
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With