Skip to content

Instantly share code, notes, and snippets.

@drovani
Last active February 18, 2022 10:19
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save drovani/df732f165d9735ad635707db1020d55d to your computer and use it in GitHub Desktop.
Save drovani/df732f165d9735ad635707db1020d55d to your computer and use it in GitHub Desktop.
Sample C# Code to Generate Shopify Multipass Url
string secret = "[shopify-multipass-secret]";
string store = "[shopify-store]";
var json = System.Text.Json.JsonSerializer.Serialize(new {
email = "[customer-email]",
created_at = DateTime.Now.ToString("O"),
identifier = "[customer-uid]",
//remote_ip = ""
});
var hash = System.Security.Cryptography.SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(secret));
var encryptionKey = new ArraySegment<byte>(hash, 0, 16).ToArray();
var signatureKey = new ArraySegment<byte>(hash, 16, 16).ToArray();
var initvector = new byte[16];
new System.Security.Cryptography.RNGCryptoServiceProvider().GetBytes(initvector);
byte[] cipherData = new Func<byte[], byte[], byte[]>((iv, key) =>
{
byte[] encrypted;
using (var aes = System.Security.Cryptography.Aes.Create())
{
aes.Key = encryptionKey;
aes.IV = iv;
aes.Mode = System.Security.Cryptography.CipherMode.CBC;
var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using (MemoryStream ms = new MemoryStream())
using (System.Security.Cryptography.CryptoStream cs = new System.Security.Cryptography.CryptoStream(ms, encryptor, System.Security.Cryptography.CryptoStreamMode.Write))
{
using (StreamWriter sw = new StreamWriter(cs))
{
sw.Write(json);
}
encrypted = ms.ToArray();
}
}
return encrypted;
})(initvector, encryptionKey);
byte[] cipher = initvector.Concat(cipherData).ToArray();
byte[] signature = new HMACSHA256(signatureKey).ComputeHash(cipher);
string token = Convert.ToBase64String(cipher.Concat(signature).ToArray()).Replace("+", "-").Replace("/", "_");
string url = $"https://{store}.myshopify.com/account/login/multipass/{token}";
@flyinmryan
Copy link

Thanks for the quick response. I did check out the documentation shortly after I asked the question and it does make sense, but I guess it's the context of my implementation that is causing me headaches. I am using .NET Framework 4.7.2 to build out a custom Shopify store that is currently live and uses the Admin Api/GraphQL queries/Javascript BuySDK, and Shopify gateway for checkout. My task now is to implement Multipass. I have activated Multipass and followed the limited example provided in their docs, as well as your solution, but no success. I wrote code from scratch at first following the steps but I only get the one recurring error from Shopify that provides no insight to the problem. I've created the customer object programmatically using Newtonsoft to convert to Json, done the encryption as instructed, but again no luck. The forums offer countless similar questions asked but no solution, which is really hard to believe. Do you actually have Multipass working with seamless login/account creation in Shopify for non-Shopify users? I am beginning to wonder if it's even possible with .NET Framework at all. Thanks

@drovani
Copy link
Author

drovani commented Dec 27, 2021

@flyinmryan - the Shopfy Multipass errors are notoriously unhelpful and terrible to debug. It has nothing to do with .NET vs React vs any other framework. My first step to debugging has usually been to pipe the output (headers & payload) to some logs and make sure it is sending what is expected.

@flyinmryan
Copy link

Last question, I promise. I have noticed a debate that's taken place on the internet between people that are smarter than I am, and even though I believe a consensus has been reached I don't know what that was. It's regarding what the garbage collector takes care of and what must be disposed of.

The SHA256 has a Create() function but is never disposed of:

var hash = System.Security.Cryptography.SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(secret));

whereas I see for the AES encryption you wrapped it in a using statement

using (var aes = System.Security.Cryptography.Aes.Create())

I take it you stand on the side of the GC handles it? I used some of your code here for multipass and someone commented about the SHA256 not being Dispose()'d.

Thanks!

@drovani
Copy link
Author

drovani commented Feb 1, 2022

Garbage collection is a complicated topic, akin to cache invalidation. There's no perfect solution, everyone has an opinion, and nothing works universally.

Having said that, unless you have large, long-running applications, chances are that you'll never really need to worry about the intricacies of GC methodologies. When an app closes, garbage collection will typically clear up everything you forgot to dispose.

Wrapping an IDisposable in a using statement is a good practice with little downside. Assuming the class implements IDisposable according to best practice, the objects will all get disposed during the finalizer.

More about that can be found at CA1063: Implement IDisposable correctly.

@flyinmryan
Copy link

Thanks again for taking the time to explain your code. It really boggles my mind how inadequate and full of traps Shopify’s API documentation and development experience are. There are no shortage of questions on their community forum, but without answers to many of them it’s counterproductive. Your code makes sense now, and had you not posted this I would have probably given up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment