Sergio Tapia
Sergio Tapia

Reputation: 9823

How can I generate a WM_SEC.AUTH_SIGNATURE in Elixir?

I'm trying to generate Walmart's API WM_SEC.AUTH_SIGNATURE header.

I found a lot of examples in Ruby and Python, but no examples in Elixir. I've been trying to create one and try it out.

Here's what I attempted.

# mix.exs
{:ex_crypto, git: "https://github.com/ntrepid8/ex_crypto.git", branch: "master"}

  def scrape_store_item(store_item) do
    consumer_id = "my-consumer-id"
    private_key_version = "1"
    private_key_password = "my-private-key-password"
    private_key =
      Application.app_dir(:my_app, "priv/keys/walmart/WM_IO_private_key.pem")
      |> ExPublicKey.load!(private_key_password)

    timestamp = DateTime.utc_now() |> DateTime.to_unix()

    url = "https://developer.api.walmart.com/api-proxy/service/affil/product/v2/taxonomy"
    message = "#{consumer_id}\n#{timestamp}\n#{private_key_version}\n"

    encoded_message = Base.encode64(message)

    {:ok, auth_signature} = ExPublicKey.sign(encoded_message, private_key)
    auth_signature = Base.encode64(auth_signature)

    middleware = [
      {Tesla.Middleware.BaseUrl,
        "https://developer.api.walmart.com/api-proxy/service/affil/product/v2"},
      {Tesla.Middleware.Headers,
        [
          {"WM_CONSUMER.ID", consumer_id},
          {"WM_CONSUMER.INTIMESTAMP", timestamp},
          {"WM_SEC.KEY_VERSION", private_key_version},
          {"WM_SEC.AUTH_SIGNATURE", auth_signature},
          {"WM_SHOW_REASON_CODES", "ALL"},
          {"Content-Type", "application/json"}
        ]}
    ]

    client = Tesla.client(middleware)

    {:ok, response} = Tesla.get(client, "/taxonomy") |> IO.inspect()
    IO.inspect(response.body)
  end

I get a 401 response with this code:

{:ok,
 %Tesla.Env{
   __client__: %Tesla.Client{
     adapter: nil,
     fun: nil,
     post: [],
     pre: [
       {Tesla.Middleware.BaseUrl, :call,
        ["https://developer.api.walmart.com/api-proxy/service/affil/product/v2"]},
       {Tesla.Middleware.Headers, :call,
        [
          [
            {"WM_CONSUMER.ID", "my-consumer-id"},
            {"WM_CONSUMER.INTIMESTAMP", 1654443660},
            {"WM_SEC.KEY_VERSION", "1"},
            {"WM_SEC.AUTH_SIGNATURE",
             "kdXG+e6R/n+8pH1ha1WKnzLrAHbUqmJsZfN9nOIyOzp6gsHAH7/VrX0K477cdzAq/v7YLpNJXZug3Yt6WTZoP17sZhz6Dig1BK1gg+EZqVqRaF3VJdRwBKlVgBO31s634xL7M8kPhXK11CsMxG8/9xjTGn2cDKEZ9aLeq15ECIfYa5tVtCdTcjNS4u6a7npByU9PIFp9a7n3h1KbW9C/9EA05kTuC1N0oS8nBlnKbA2+C0UW9EAvN4MaIkG0SqOqf/uEHn9BteAv8hI0Ayyny9RpJQmfZEpZ0G3htA7t1pWTzwxUsIJrF/5D1gV+IIYR7OiwHUg2RsIrnPohbznPQw=="}
          ]
        ]}
     ]
   },
   __module__: Tesla,
   body: <<31, 139, 8, 0, 0, 9, 110, 136, 0, 255, 68, 204, 65, 138, 131, 64, 16,
     70, 225, 171, 20, 255, 122, 70, 156, 25, 199, 69, 175, 115, 132, 236, 67,
     97, 151, 177, 192, 110, 155, 174, 210, 4, 196, 187, 135, ...>>,
   headers: [
     {"content-encoding", "gzip"},
     {"content-type", "application/json;charset=utf-8"},
     {"last-modified", "Sun, 05 Jun 2022 15:41:00 GMT"},
     {"strict-transport-security", "max-age=86400"},
     {"wm_svc.env", "prod"},
     {"wm_svc.name", "affil-product"},
     {"wm_svc.version", "2.0.0"},
     {"x-lua-strict-transport-security", "max-age=86400"},
     {"x-tb", "1"},
     {"x-tb-optimization-total-bytes-saved", "0"},
     {"date", "Sun, 05 Jun 2022 15:41:00 GMT"},
     {"connection", "close"},
     {"set-cookie",
      "TS01a35e2a=01c5a4e2f95f0b472a3a7606aa7c7c33653874c13d636655443ecbca84d23369b19bc1de1973ac24c93ff1f24512e7af49264d46c6; Path=/; Secure"}
   ],
   method: :get,
   opts: [],
   query: [],
   status: 401,
   url: "https://developer.api.walmart.com/api-proxy/service/affil/product/v2/taxonomy"
 }}

Here's the example code someone shared for a working Ruby version.

version = 'YOUR VERSION'
consumer_id = "YOUR CONSUMER ID"
time_stamp = (Time.now.to_i * 1000).to_s
p_key = "YOUR PRIVATE KEY"
digest = OpenSSL::Digest.new('sha256')

data = consumer_id + "\n" + time_stamp + "\n" + version + "\n"

k = OpenSSL::PKey::RSA.new(p_key.to_s)
digest = OpenSSL::Digest::SHA256.new
signature = k.sign(digest,data)
signature = Base64.strict_encode64(signature)

headers = {
  "WM_SEC.KEY_VERSION": version,
  "WM_CONSUMER.ID": consumer_id,
  "WM_CONSUMER.INTIMESTAMP": time_stamp,
  "WM_SEC.AUTH_SIGNATURE": signature
}

puts HTTParty.get("https://developer.api.walmart.com/api-proxy/service/affil/product/v2/taxonomy", headers: headers).parsed_response

Upvotes: 1

Views: 168

Answers (2)

Adam Millerchip
Adam Millerchip

Reputation: 23091

Here's a script that does it without ex_crypto. Nice challenge, those Wallmart docs are terrible.

Mix.install([:tesla])

key_version = System.fetch_env!("WALLMART_KEY_VERSION")
consumer_id = System.fetch_env!("WALLMART_CONSUMER_ID")
private_key_pem = File.read!("WM_IO_private_key.pem")

[pem_entry] = :public_key.pem_decode(private_key_pem)
private_key = :public_key.pem_entry_decode(pem_entry)
timestamp = System.os_time(:millisecond)

auth_signature =
  "#{consumer_id}\n#{timestamp}\n#{key_version}\n"
  |> :public_key.sign(:sha256, private_key)
  |> Base.encode64()

url = "https://developer.api.walmart.com/api-proxy/service/affil/product/v2/taxonomy"

headers = [
  {"WM_CONSUMER.ID", consumer_id},
  {"WM_CONSUMER.INTIMESTAMP", timestamp},
  {"WM_SEC.KEY_VERSION", key_version},
  {"WM_SEC.AUTH_SIGNATURE", auth_signature}
]

{:ok, %{body: body}} = Tesla.get(url, headers: headers)
IO.puts(body)

Output:

{"categories":[{"id":"0","name":"Home Page","path":"Home Page",...

Upvotes: 2

Only Bolivian Here
Only Bolivian Here

Reputation: 36743

Here's how you generate WM_SEC.AUTH_SIGNATURE in Elixir:

Make sure you have ex_crypto package installed from master branch since the latest version has necessary changes but is not published.

{:ex_crypto, git: "https://github.com/ntrepid8/ex_crypto.git", branch: "master"}

Then here's the solution:

version = "1"
consumer_id = "my-consumer-id"
timestamp = DateTime.utc_now() |> DateTime.to_unix(:millisecond)

data = "#{consumer_id}\n#{timestamp}\n#{version}\n"

private_key =
  Application.app_dir(:my_app, "priv/keys/walmart/WM_IO_private_key.pem")
  |> ExPublicKey.load!()

{:ok, auth_signature} = ExPublicKey.sign(data, private_key)
auth_signature = Base.encode64(auth_signature)

middleware = [
  {Tesla.Middleware.BaseUrl,
    "https://developer.api.walmart.com/api-proxy/service/affil/product/v2"},
  {Tesla.Middleware.Headers,
    [
      {"WM_CONSUMER.ID", consumer_id},
      {"WM_CONSUMER.INTIMESTAMP", timestamp},
      {"WM_SEC.KEY_VERSION", version},
      {"WM_SEC.AUTH_SIGNATURE", auth_signature}
    ]}
]

client = Tesla.client(middleware)

Tesla.get(client, "/taxonomy")

Upvotes: 0

Related Questions