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:
- crypto — OTP 29.0.2 (crypto 5.9)
- https://stackoverflow.com/questions/37629194/how-to-encrypt-and-decrypt-with-aes-cbc-128-in-elixir
Does anyone have some pointers on converting this? Thanks!
Marked As Solved
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
PR for exshopify was merged and is available in version 0.9.0: exshopify | Hex
PR for Adds Multipass functionality by fireproofsocks · Pull Request #65 · nsweeting/shopify · GitHub
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
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}"








