fireproofsocks

fireproofsocks

Shopify Multipass : Elixir Implementation

I’m working on an Elixir implementation of Shopify’s Multipass feature – it’s basically a single-sign-on (SSO) flow that lets your app handle authentication, and on success, it produces a URL that when clicked will cause a customer record to be upserted into Shopify. They include working examples of how to do this in PHP and Ruby, but I cannot make it work in Elixir.

You have to enable the feature and they give you a 32 character shared secret key.

# Example key from Shopify Admin
multipass_secret = "1234567890abcdef1234567890abcdef"

customer_data = %{
  email: "test@test.shopify.com",
  created_at: DateTime.to_iso8601(Timex.now()),  # <-- the token will only be valid for a small timeframe around this timestamp.
}

key_material = :crypto.hash(:sha256, multipass_secret)
# Split the key into 2 binaries each containing exactly 16 bytes
<< encryption_key::binary-size(16), signature_key::binary-size(16) >> = key_material

customer_data_as_string = Jason.encode!(customer_data)

# Initialization Vector
ivec = :crypto.strong_rand_bytes(16)

to_add = 16 - rem(byte_size(customer_data_as_string), 16)
padded = customer_data_as_string <> :binary.copy(<<to_add>>, to_add)

cipher_text = ivec <> :crypto.block_encrypt(:aes_cbc128, encryption_key, ivec, padded)
signature = :crypto.hmac(:sha256, signature_key, cipher_text)
message = cipher_text <> signature

token = Base.encode16(message, case: :lower)

"https://yourstore.myshopify.com/account/login/multipass/#{token}"

Note that this feature is only available in Shopify Plus accounts.

Links I’ve been pouring over:

Does anyone have some pointers on converting this? Thanks!

Marked As Solved

Ninigi

Ninigi

Before diving too deep into this, can you try

token = Base.url_encode64(message)

EDIT

I took the provided ruby code on the Shopify documentation, replaced dynamic values with static values (the json string and iv) and used the resulting token to set up a simple test.

I think you already had it:

multipass_secret = "1234567890abcdef1234567890abcdef"

customer_data = %{
  email: "test@test.shopify.com",
  created_at: "2019-03-07T11:34:51.153+09:00"
}

key_material = :crypto.hash(:sha256, multipass_secret)
# Split the key into 2 binaries each containing exactly 16 bytes
<< encryption_key::binary-size(16), signature_key::binary-size(16) >> = key_material

customer_data_as_string = Jason.encode!(customer_data)

# Initialization Vector
ivec = "testieivphrasefo" # :crypto.strong_rand_bytes(16)

to_add = 16 - rem(byte_size(customer_data_as_string), 16)
padded = customer_data_as_string <> :binary.copy(<<to_add>>, to_add)

cipher_text = ivec <> :crypto.block_encrypt(:aes_cbc128, encryption_key, ivec, padded)

signature = :crypto.hmac(:sha256, signature_key, cipher_text)

message = cipher_text <> signature

Base.url_encode64(message)
# Should give you:
# "dGVzdGllaXZwaHJhc2VmbxNXNKV-epmKd9gZPxrsFbqUbYcYVFjtPSleoB-61FpmhCtibtr8cxudaPBvk8QBqdggqsOofjr21PdwM4qvHNNJh8jyMzH9emUfjxNLj2M6dZ5BGyBlFhF7OWQHWljxQywhqP7RCC0klKG_zYwlNm8="

Just change Base.encode16/2 to Base.url_encode64/1

Also Liked

fireproofsocks

fireproofsocks

fireproofsocks

fireproofsocks

WOW. That has to be the best typo I’ve made in weeks! Base 16?!? I was so focused on the hard parts of the crypto stuff that I didn’t pay attention to the last bit. THANK YOU!!

fireproofsocks

fireproofsocks

For the record, here is the complete working proof-of-concept code:

# Use your multipass secret from the Shopify Dashboard: Settings -> Checkout 
multipass_secret = "1234567890abcdef1234567890abcdef"
block_size = 16

customer_data = %{
  email: "test@test.shopify.com",
  created_at: DateTime.to_iso8601(Timex.now()), # Must be a current time
}

# Split the secret into 2 binary keys each containing exactly 16 bytes
key_material = :crypto.hash(:sha256, multipass_secret)
<< encryption_key::binary-size(16), signature_key::binary-size(16) >> = key_material

# Encode the message payload
customer_data_as_string = Jason.encode!(customer_data)

# Initialization Vector
ivec = :crypto.strong_rand_bytes(block_size)

# Padding
to_add = block_size - rem(byte_size(customer_data_as_string), block_size)
padded = customer_data_as_string <> :binary.copy(<<to_add>>, to_add)

# Manually pad the message with the IV
cipher_text = ivec <> :crypto.block_encrypt(:aes_cbc128, encryption_key, ivec, padded)
signature = :crypto.hmac(:sha256, signature_key, cipher_text)
message = cipher_text <> signature

token = Base.url_encode64(message, case: :lower)

# The magic multipass link to your site:
"https://yoursite.myshopify.com/account/login/multipass/#{token}"

Last Post!

fireproofsocks

fireproofsocks

Where Next?

Popular in Questions Top

vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New

Other popular topics Top

Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

We're in Beta

About us Mission Statement