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}"

Where Next?

Popular in Questions Top

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
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
earth10
Hi, I’m just starting to build a side-project with Elixir and Phoenix and doing some basic test with Elixir alone. What strikes me is th...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
jaysoifer
Is there a way to rollback a specific migration and only that one (“skipping” all the other ones)? Would mix ecto.rollback -v 200809061...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lists...
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
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
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New

Other popular topics Top

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
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
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
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
AstonJ
Please see the new poll here: Which code editor or IDE do you use? (Poll) (2022 Edition) It’s been a while since we first asked this, I...
208 31142 143
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
marick
I had some trouble figuring out how to make many-to-many associations work. Once I got it working, I wrote a blog post. Because I’m a nov...
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