Skip to content

Commit 7d8dae9

Browse files
committed
Support using the Storefront private access token
This allows for using [the Storefront private access token][1] with the library's Storefront GraphQL client. Since this client is probably going to be running on a server where the token can be secured, using the private access token should be preferred. This commit doesn't make using the private token the default to avoid an immediate breaking change. We want to give clients time to see the deprecation and update their scripts. Closes #1293 [1]: https://shopify.dev/docs/api/usage/authentication#getting-started-with-private-access
1 parent 0d6ad53 commit 7d8dae9

File tree

5 files changed

+98
-10
lines changed

5 files changed

+98
-10
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Note: For changes to the API, see https://shopify.dev/changelog?filter=api
77
- [#1071](https://github.com/Shopify/shopify-api-ruby/issues/1071) Fix FulfillmentEvent class types
88
- Fix: InventoryItem class `harmonized_system_code` attribute type which can be either integer, string or nil
99
- Fix: Variant class `inventory_quantity` attribute type which can be either integer, string or nil
10+
- [1293](https://github.com/Shopify/shopify-api-ruby/issues/1293) Add support for using Storefront private access tokens.
11+
- Deprecated passing the public Storefront access token as a positional parameter to the Storefront GraphQL client in favor of using the named parameter. (You probably want to use the private access token for this client anyway.)
1012

1113
## 14.0.1
1214
- [#1288](https://github.com/Shopify/shopify-api-ruby/pull/1288) Fix FeatureDeprecatedError being raised without a message.

Gemfile.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ GEM
139139

140140
PLATFORMS
141141
arm64-darwin-21
142+
arm64-darwin-23
142143
universal-darwin-22
143144
x86_64-linux
144145

docs/usage/graphql_storefront.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
# Make a Storefront API call
22

3-
The library also allows you to send GraphQL requests to the [Shopify Storefront API](https://shopify.dev/docs/api/storefront). To do that, you can use `ShopifyAPI::Clients::Graphql::Storefront` with the current session and a `storefrontAccessToken`.
3+
The library also allows you to send GraphQL requests to the [Shopify Storefront API](https://shopify.dev/docs/api/storefront). To do that, you can use `ShopifyAPI::Clients::Graphql::Storefront` with either a [private or public Storefront access token](https://shopify.dev/docs/api/usage/authentication#access-tokens-for-the-storefront-api).
44

55
You can obtain Storefront API access tokens for both private apps and sales channels. Please read [our documentation](https://shopify.dev/docs/custom-storefronts/building-with-the-storefront-api/getting-started) to learn more about Storefront Access Tokens.
66

77
Below is an example of how you may query the Storefront API:
88

99
```ruby
1010
# Load the access token as per instructions above
11-
storefront_access_token = ''
11+
storefront_private_access_token = ''
1212
# your shop domain
1313
shop_url = 'shop.myshopify.com'
1414

15-
# initialize the client with session and storefront access token
16-
client = ShopifyAPI::Clients::Graphql::Storefront.new(shop_url, storefront_access_token)
15+
# initialize the client with session and a private Storefront access token
16+
client = ShopifyAPI::Clients::Graphql::Storefront.new(shop_url, private_token: storefront_private_access_token)
17+
# or, alternatively with a public Storefront access token:
18+
# client = ShopifyAPI::Clients::Graphql::Storefront.new(shop_url, public_token: storefront_public_access_token)
1719

1820
query = <<~QUERY
1921
{
@@ -35,14 +37,19 @@ query = <<~QUERY
3537
}
3638
QUERY
3739

38-
response = client.query(query: query)
40+
# You may not need the "Shopify-Storefront-Buyer-IP" header, see its documentation:
41+
# https://shopify.dev/docs/api/usage/authentication#making-server-side-requests
42+
response = client.query(query: query, headers: { "Shopify-Storefront-Buyer-IP": request.ip })
3943
# do something with the returned data
4044
```
4145

4246
By default, the client uses the API version configured in `ShopifyAPI`. To use a different API version, set the optional `api_version` parameter. To experiment with prerelease API features, use `"unstable"` for the API version.
4347

4448
```ruby
45-
client = ShopifyAPI::Clients::Graphql::Storefront.new(shop_url, storefront_access_token, api_version: "unstable")
49+
client = ShopifyAPI::Clients::Graphql::Storefront.new(shop_url,
50+
private_token: storefront_private_access_token,
51+
api_version: "unstable"
52+
)
4653
```
4754

4855
Want to make calls to the Admin API? Click [here](graphql.md)

lib/shopify_api/clients/graphql/storefront.rb

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,38 @@ module ShopifyAPI
55
module Clients
66
module Graphql
77
class Storefront < Client
8-
sig { params(shop: String, storefront_access_token: String, api_version: T.nilable(String)).void }
9-
def initialize(shop, storefront_access_token, api_version: nil)
8+
sig do
9+
params(
10+
shop: String,
11+
storefront_access_token: T.nilable(String),
12+
private_token: T.nilable(String),
13+
public_token: T.nilable(String),
14+
api_version: T.nilable(String),
15+
).void
16+
end
17+
def initialize(shop, storefront_access_token = nil, private_token: nil, public_token: nil, api_version: nil)
18+
unless storefront_access_token.nil?
19+
warning = <<~WARNING
20+
DEPRECATED: Use the named parameters for the Storefront token instead of passing
21+
the public token as the second argument. Also, you may want to look into using
22+
the Storefront private access token instead:
23+
https://shopify.dev/docs/api/usage/authentication#getting-started-with-private-access
24+
WARNING
25+
ShopifyAPI::Logger.deprecated(warning, "15.0.0")
26+
end
27+
1028
session = Auth::Session.new(
1129
id: shop,
1230
shop: shop,
1331
access_token: "",
1432
is_online: false,
1533
)
1634
super(session: session, base_path: "/api", api_version: api_version)
17-
@storefront_access_token = storefront_access_token
35+
@storefront_access_token = T.let(T.must(private_token || public_token || storefront_access_token), String)
36+
@storefront_auth_header = T.let(
37+
private_token.nil? ? "X-Shopify-Storefront-Access-Token" : "Shopify-Storefront-Private-Token",
38+
String,
39+
)
1840
end
1941

2042
sig do
@@ -26,7 +48,7 @@ def initialize(shop, storefront_access_token, api_version: nil)
2648
).returns(HttpResponse)
2749
end
2850
def query(query:, variables: nil, headers: {}, tries: 1)
29-
T.must(headers).merge!({ "X-Shopify-Storefront-Access-Token": @storefront_access_token })
51+
T.must(headers).merge!({ @storefront_auth_header => @storefront_access_token })
3052
super(query: query, variables: variables, headers: headers, tries: tries)
3153
end
3254
end

test/clients/graphql/storefront_test.rb

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,62 @@ def build_client
2727
ShopifyAPI::Clients::Graphql::Storefront.new(@shop, @storefront_access_token, api_version: @api_version)
2828
end
2929
end
30+
31+
def test_can_query_using_private_token
32+
query = <<~QUERY
33+
{
34+
shop {
35+
name
36+
}
37+
}
38+
QUERY
39+
extra_headers = { extra: "header" }
40+
body = { query: query, variables: nil }
41+
success_body = { "success" => true }
42+
response_headers = { "content-type" => "application/json" }
43+
44+
@client = ShopifyAPI::Clients::Graphql::Storefront.new(@shop, private_token: "private_token")
45+
@expected_headers = TestHelpers::Constants::DEFAULT_CLIENT_HEADERS.merge({
46+
"Shopify-Storefront-Private-Token": "private_token",
47+
}).merge(extra_headers)
48+
49+
stub_request(:post, "https://test-shop.myshopify.com/#{@path}/#{ShopifyAPI::Context.api_version}/graphql.json")
50+
.with(body: body, headers: @expected_headers)
51+
.to_return(body: success_body.to_json, headers: response_headers)
52+
53+
response = @client.query(query: query, headers: extra_headers)
54+
assert_equal(success_body, response.body)
55+
end
56+
57+
def test_can_query_using_public_token
58+
query = <<~QUERY
59+
{
60+
shop {
61+
name
62+
}
63+
}
64+
QUERY
65+
extra_headers = { extra: "header" }
66+
body = { query: query, variables: nil }
67+
success_body = { "success" => true }
68+
response_headers = { "content-type" => "application/json" }
69+
70+
@client = ShopifyAPI::Clients::Graphql::Storefront.new(@shop, public_token: "public_token")
71+
@expected_headers = TestHelpers::Constants::DEFAULT_CLIENT_HEADERS.merge({
72+
"X-Shopify-Storefront-Access-Token": "public_token",
73+
}).merge(extra_headers)
74+
75+
stub_request(:post, "https://test-shop.myshopify.com/#{@path}/#{ShopifyAPI::Context.api_version}/graphql.json")
76+
.with(body: body, headers: @expected_headers)
77+
.to_return(body: success_body.to_json, headers: response_headers)
78+
79+
response = @client.query(query: query, headers: extra_headers)
80+
assert_equal(success_body, response.body)
81+
end
82+
83+
def test_error_raised_when_no_token_provided
84+
assert_raises(TypeError) { ShopifyAPI::Clients::Graphql::Storefront.new(@shop) }
85+
end
3086
end
3187
end
3288
end

0 commit comments

Comments
 (0)