Skip to content

Commit 90de9ce

Browse files
langsharpeclaude
andcommitted
Use X-goog-api-key header instead of URL parameter for API authentication
This change implements Google's recommended REST API authentication practice by using the X-goog-api-key header instead of passing the API key as a URL parameter. This improves security and follows modern API standards. Changes: - Added add_auth_header method to set X-goog-api-key header - Updated get and post methods to use header authentication - Modified generate_auth_url to exclude API key from URL parameters - Updated all test specs to expect header-based authentication - Client ID authentication continues to work unchanged Fixes #3 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 7bda933 commit 90de9ce

9 files changed

Lines changed: 148 additions & 61 deletions

File tree

lib/google_maps_service/client.rb

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ def get(path, params, base_url: DEFAULT_BASE_URL, accepts_client_id: true, custo
142142
request_query_ticket
143143
request = Net::HTTP::Get.new(url)
144144
request["User-Agent"] = user_agent
145+
add_auth_header(request, accepts_client_id)
145146
response = Net::HTTP.start(url.hostname, url.port, use_ssl: url.scheme == "https") do |http|
146147
http.request(request)
147148
end
@@ -171,6 +172,7 @@ def post(path, params, base_url: DEFAULT_BASE_URL, accepts_client_id: true, cust
171172
request_query_ticket
172173
request = Net::HTTP::Post.new(url)
173174
request["User-Agent"] = user_agent
175+
add_auth_header(request, accepts_client_id)
174176
request["X-Goog-FieldMask"] = field_mask if field_mask
175177
request["Content-Type"] = "application/json"
176178
request.body = MultiJson.dump(params)
@@ -204,6 +206,26 @@ def release_query_ticket
204206
@qps_queue << Time.now if @qps_queue
205207
end
206208

209+
# Add authentication headers to the request.
210+
#
211+
# @param [Net::HTTPRequest] request The HTTP request object.
212+
# @param [Boolean] accepts_client_id Sign the request using API {#keys} instead of {#client_id}.
213+
#
214+
# @return [void]
215+
def add_auth_header(request, accepts_client_id)
216+
if accepts_client_id && @client_id && @client_secret
217+
# Client ID authentication doesn't use headers
218+
return
219+
end
220+
221+
if @key
222+
request["X-goog-api-key"] = @key
223+
return
224+
end
225+
226+
raise ArgumentError, "Must provide API key for this API. It does not accept enterprise credentials."
227+
end
228+
207229
# Returns the path and query string portion of the request URL,
208230
# first adding any necessary parameters.
209231
#
@@ -229,9 +251,9 @@ def generate_auth_url(path, params, accepts_client_id)
229251
return path + "&signature=" + sig
230252
end
231253

254+
# For API key authentication, we now use headers instead of URL parameters
232255
if @key
233-
params << ["key", @key]
234-
return path + "?" + GoogleMapsService::Url.urlencode_params(params)
256+
return params.empty? ? path : path + "?" + GoogleMapsService::Url.urlencode_params(params)
235257
end
236258

237259
raise ArgumentError, "Must provide API key for this API. It does not accept enterprise credentials."

spec/google_maps_service/apis/directions_spec.rb

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@
55

66
before(:example) do
77
stub_request(:get, /https:\/\/maps.googleapis.com\/maps\/api\/directions\/.*/)
8+
.with(headers: {"X-goog-api-key" => api_key})
89
.to_return(status: 200, headers: {"Content-Type" => "application/json"}, body: '{"status":"OK","results":[]}')
910
end
1011

1112
context "simple directions" do
1213
it "should call Google Maps Web Service" do
1314
# Simplest directions request. Driving directions by default.
1415
client.directions("Sydney", "Melbourne")
15-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Sydney&destination=Melbourne&key=%s" %
16-
api_key)).to have_been_made
16+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Sydney&destination=Melbourne")
17+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
1718
end
1819
end
1920

@@ -24,8 +25,8 @@
2425
avoid: ["highways", "tolls", "ferries"],
2526
units: "metric",
2627
region: "au")
27-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Sydney&avoid=highways%%7Ctolls%%7Cferries&destination=Melbourne&mode=bicycling&key=%s&units=metric&region=au" %
28-
api_key)).to have_been_made
28+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Sydney&avoid=highways%%7Ctolls%%7Cferries&destination=Melbourne&mode=bicycling&units=metric&region=au")
29+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
2930
end
3031
end
3132

@@ -35,8 +36,8 @@
3536
client.directions("Sydney Town Hall", "Parramatta, NSW",
3637
mode: "transit",
3738
departure_time: now)
38-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Sydney+Town+Hall&key=%s&destination=Parramatta%%2C+NSW&mode=transit&departure_time=%d" %
39-
[api_key, now.to_i])).to have_been_made
39+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Sydney+Town+Hall&destination=Parramatta%%2C+NSW&mode=transit&departure_time=%d" % now.to_i)
40+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
4041
end
4142
end
4243

@@ -46,8 +47,8 @@
4647
client.directions("Sydney Town Hall", "Parramatta, NSW",
4748
mode: "transit",
4849
arrival_time: an_hour_before_now)
49-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Sydney+Town+Hall&arrival_time=%d&destination=Parramatta%%2C+NSW&mode=transit&key=%s" %
50-
[an_hour_before_now.to_i, api_key])).to have_been_made
50+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Sydney+Town+Hall&arrival_time=%d&destination=Parramatta%%2C+NSW&mode=transit" % an_hour_before_now.to_i)
51+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
5152
end
5253
end
5354

@@ -77,7 +78,8 @@
7778
it "should call Google Maps Web Service" do
7879
client.directions("Town Hall, Sydney", "Parramatta, NSW",
7980
mode: "bicycling")
80-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Town+Hall%%2C+Sydney&destination=Parramatta%%2C+NSW&mode=bicycling&key=%s" % api_key)).to have_been_made
81+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Town+Hall%%2C+Sydney&destination=Parramatta%%2C+NSW&mode=bicycling")
82+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
8183
end
8284
end
8385

@@ -87,15 +89,17 @@
8789
client.directions("Brooklyn", "Queens",
8890
mode: "transit",
8991
departure_time: now)
90-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Brooklyn&key=%s&destination=Queens&mode=transit&departure_time=%d" % [api_key, now.to_i])).to have_been_made
92+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Brooklyn&destination=Queens&mode=transit&departure_time=%d" % now.to_i)
93+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
9194
end
9295
end
9396

9497
context "boston to concord via charlestown and lexington" do
9598
it "should call Google Maps Web Service" do
9699
client.directions("Boston, MA", "Concord, MA",
97100
waypoints: ["Charlestown, MA", "Lexington, MA"])
98-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Boston%%2C+MA&destination=Concord%%2C+MA&waypoints=Charlestown%%2C+MA%%7CLexington%%2C+MA&key=%s" % api_key)).to have_been_made
101+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Boston%%2C+MA&destination=Concord%%2C+MA&waypoints=Charlestown%%2C+MA%%7CLexington%%2C+MA")
102+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
99103
end
100104
end
101105

@@ -107,22 +111,24 @@
107111
"Connawarra, SA",
108112
"McLaren Vale, SA"],
109113
optimize_waypoints: true)
110-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Adelaide%%2C+SA&destination=Adelaide%%2C+SA&waypoints=optimize%%3Atrue%%7CBarossa+Valley%%2C+SA%%7CClare%%2C+SA%%7CConnawarra%%2C+SA%%7CMcLaren+Vale%%2C+SA&key=%s" % api_key)).to have_been_made
114+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Adelaide%%2C+SA&destination=Adelaide%%2C+SA&waypoints=optimize%%3Atrue%%7CBarossa+Valley%%2C+SA%%7CClare%%2C+SA%%7CConnawarra%%2C+SA%%7CMcLaren+Vale%%2C+SA")
115+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
111116
end
112117
end
113118

114119
context "toledo to madrid in spain" do
115120
it "should call Google Maps Web Service" do
116121
client.directions("Toledo", "Madrid",
117122
region: "es")
118-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Toledo&region=es&destination=Madrid&key=%s" %
119-
api_key)).to have_been_made
123+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Toledo&region=es&destination=Madrid")
124+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
120125
end
121126
end
122127

123128
context "zero results returns response" do
124129
before(:example) do
125130
stub_request(:get, /https:\/\/maps.googleapis.com\/maps\/api\/directions\/.*/)
131+
.with(headers: {"X-goog-api-key" => api_key})
126132
.to_return(status: 200, headers: {"Content-Type" => "application/json"}, body: '{"status":"ZERO_RESULTS","routes":[]}')
127133
end
128134

@@ -135,6 +141,7 @@
135141
context "can return full response" do
136142
before(:example) do
137143
stub_request(:get, /https:\/\/maps.googleapis.com\/maps\/api\/directions\/.*/)
144+
.with(headers: {"X-goog-api-key" => api_key})
138145
.to_return(status: 200, headers: {"Content-Type" => "application/json"}, body: '{"status":"OK","routes":[]}')
139146
end
140147

@@ -149,15 +156,17 @@
149156
client.directions("Toledo", "Madrid",
150157
region: "es",
151158
language: "es")
152-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Toledo&region=es&destination=Madrid&key=%s&language=es" % api_key)).to have_been_made
159+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Toledo&region=es&destination=Madrid&language=es")
160+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
153161
end
154162
end
155163

156164
context "alternatives" do
157165
it "should call Google Maps Web Service" do
158166
client.directions("Sydney Town Hall", "Parramatta Town Hall",
159167
alternatives: true)
160-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Sydney+Town+Hall&destination=Parramatta+Town+Hall&alternatives=true&key=%s" % api_key)).to have_been_made
168+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/directions/json?origin=Sydney+Town+Hall&destination=Parramatta+Town+Hall&alternatives=true")
169+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
161170
end
162171
end
163172
end

spec/google_maps_service/apis/distance_matrix_spec.rb

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
before(:example) do
77
stub_request(:get, /https:\/\/maps.googleapis.com\/maps\/api\/distancematrix\/.*/)
8+
.with(headers: {"X-goog-api-key" => api_key})
89
.to_return(status: 200, headers: {"Content-Type" => "application/json"}, body: '{"status":"OK","rows":[]}')
910
end
1011

@@ -21,7 +22,8 @@
2122
"The Pinnacles, Australia"]
2223

2324
client.distance_matrix(origins, destinations)
24-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/distancematrix/json?key=%s&origins=Perth%%2C+Australia%%7CSydney%%2C+Australia%%7CMelbourne%%2C+Australia%%7CAdelaide%%2C+Australia%%7CBrisbane%%2C+Australia%%7CDarwin%%2C+Australia%%7CHobart%%2C+Australia%%7CCanberra%%2C+Australia&destinations=Uluru%%2C+Australia%%7CKakadu%%2C+Australia%%7CBlue+Mountains%%2C+Australia%%7CBungle+Bungles%%2C+Australia%%7CThe+Pinnacles%%2C+Australia" % api_key)).to have_been_made
25+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/distancematrix/json?origins=Perth%%2C+Australia%%7CSydney%%2C+Australia%%7CMelbourne%%2C+Australia%%7CAdelaide%%2C+Australia%%7CBrisbane%%2C+Australia%%7CDarwin%%2C+Australia%%7CHobart%%2C+Australia%%7CCanberra%%2C+Australia&destinations=Uluru%%2C+Australia%%7CKakadu%%2C+Australia%%7CBlue+Mountains%%2C+Australia%%7CBungle+Bungles%%2C+Australia%%7CThe+Pinnacles%%2C+Australia")
26+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
2527
end
2628
end
2729

@@ -32,7 +34,8 @@
3234
{lat: 42.8863855, lng: -78.8781627}]
3335

3436
client.distance_matrix(origins, destinations)
35-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/distancematrix/json?key=%s&origins=Bobcaygeon+ON%%7C41.432060%%2C-81.389920&destinations=43.012486%%2C-83.696415%%7C42.886386%%2C-78.878163" % api_key)).to have_been_made
37+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/distancematrix/json?origins=Bobcaygeon+ON%%7C41.432060%%2C-81.389920&destinations=43.012486%%2C-83.696415%%7C42.886386%%2C-78.878163")
38+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
3639
end
3740
end
3841

@@ -53,7 +56,8 @@
5356
language: "en-AU",
5457
avoid: "tolls",
5558
units: "imperial")
56-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/distancematrix/json?origins=Perth%%2C+Australia%%7CSydney%%2C+Australia%%7CMelbourne%%2C+Australia%%7CAdelaide%%2C+Australia%%7CBrisbane%%2C+Australia%%7CDarwin%%2C+Australia%%7CHobart%%2C+Australia%%7CCanberra%%2C+Australia&language=en-AU&avoid=tolls&mode=driving&key=%s&units=imperial&destinations=Uluru%%2C+Australia%%7CKakadu%%2C+Australia%%7CBlue+Mountains%%2C+Australia%%7CBungle+Bungles%%2C+Australia%%7CThe+Pinnacles%%2C+Australia" % api_key)).to have_been_made
59+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/distancematrix/json?origins=Perth%%2C+Australia%%7CSydney%%2C+Australia%%7CMelbourne%%2C+Australia%%7CAdelaide%%2C+Australia%%7CBrisbane%%2C+Australia%%7CDarwin%%2C+Australia%%7CHobart%%2C+Australia%%7CCanberra%%2C+Australia&language=en-AU&avoid=tolls&mode=driving&units=imperial&destinations=Uluru%%2C+Australia%%7CKakadu%%2C+Australia%%7CBlue+Mountains%%2C+Australia%%7CBungle+Bungles%%2C+Australia%%7CThe+Pinnacles%%2C+Australia")
60+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
5761
end
5862
end
5963

@@ -65,8 +69,8 @@
6569
client.distance_matrix(origins, destinations,
6670
language: "fr-FR",
6771
mode: "bicycling")
68-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/distancematrix/json?key=%s&language=fr-FR&mode=bicycling&origins=Vancouver+BC%%7CSeattle&destinations=San+Francisco%%7CVictoria+BC" %
69-
api_key)).to have_been_made
72+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/distancematrix/json?language=fr-FR&mode=bicycling&origins=Vancouver+BC%%7CSeattle&destinations=San+Francisco%%7CVictoria+BC")
73+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
7074
end
7175
end
7276

spec/google_maps_service/apis/elevation_spec.rb

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,40 @@
55

66
before(:example) do
77
stub_request(:get, /https:\/\/maps.googleapis.com\/maps\/api\/elevation\/.*/)
8+
.with(headers: {"X-goog-api-key" => api_key})
89
.to_return(status: 200, headers: {"Content-Type" => "application/json"}, body: '{"status":"OK","results":[]}')
910
end
1011

1112
context "elevation single" do
1213
it "should call Google Maps Web Service" do
1314
client.elevation([40.714728, -73.998672])
14-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/elevation/json?locations=40.714728%%2C-73.998672&key=%s" % api_key)).to have_been_made
15+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/elevation/json?locations=40.714728%%2C-73.998672")
16+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
1517
end
1618
end
1719

1820
context "elevation single list" do
1921
it "should call Google Maps Web Service" do
2022
client.elevation([[40.714728, -73.998672]])
21-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/elevation/json?locations=40.714728%%2C-73.998672&key=%s" % api_key)).to have_been_made
23+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/elevation/json?locations=40.714728%%2C-73.998672")
24+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
2225
end
2326
end
2427

2528
context "elevation multiple" do
2629
it "should call Google Maps Web Service" do
2730
locations = [[40.714728, -73.998672], [-34.397, 150.644]]
2831
client.elevation(locations)
29-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/elevation/json?locations=40.714728%%2C-73.998672%%7C-34.397000%%2C150.644000&key=%s" % api_key)).to have_been_made
32+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/elevation/json?locations=40.714728%%2C-73.998672%%7C-34.397000%%2C150.644000")
33+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
3034
end
3135
end
3236

3337
context "elevation along path" do
3438
context "with single point" do
3539
before(:example) do
3640
stub_request(:get, /https:\/\/maps.googleapis.com\/maps\/api\/elevation\/.*/)
41+
.with(headers: {"X-goog-api-key" => api_key})
3742
.to_return(status: 200, headers: {"Content-Type" => "application/json"}, body: '{"results": [], "status": "INVALID_REQUEST"}')
3843
end
3944

@@ -47,7 +52,8 @@
4752
path = [[40.714728, -73.998672], [-34.397, 150.644]]
4853

4954
client.elevation_along_path(path, 5)
50-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/elevation/json?path=40.714728%%2C-73.998672%%7C-34.397000%%2C150.644000&key=%s&samples=5" % api_key)).to have_been_made
55+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/elevation/json?path=40.714728%%2C-73.998672%%7C-34.397000%%2C150.644000&samples=5")
56+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
5157
end
5258
end
5359

@@ -56,7 +62,8 @@
5662
path = "gfo}EtohhUxD@bAxJmGF"
5763

5864
client.elevation_along_path(path, 5)
59-
expect(a_request(:get, "https://maps.googleapis.com/maps/api/elevation/json?path=enc:gfo}EtohhUxD@bAxJmGF&key=%s&samples=5" % api_key)).to have_been_made
65+
expect(a_request(:get, "https://maps.googleapis.com/maps/api/elevation/json?path=enc:gfo}EtohhUxD@bAxJmGF&samples=5")
66+
.with(headers: {"X-goog-api-key" => api_key})).to have_been_made
6067
end
6168
end
6269
end

0 commit comments

Comments
 (0)