Skip to content

Instantly share code, notes, and snippets.

@dbnoble
Created January 30, 2016 11:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dbnoble/1120dafcf44cc8f60f46 to your computer and use it in GitHub Desktop.
Save dbnoble/1120dafcf44cc8f60f46 to your computer and use it in GitHub Desktop.
Authentication without accounts for froatsnook:shopify
// define the Shops collection on the client and the server
Shops = new Mongo.Collection("shops");
// This can be anywhere you need to initialize the API on the client.
// ie. Template.rendered callback, inside it's own function, etc
MyAPIFunctionality = function() {
var api_key = envAPIKey;
var shop = Session.get("shop"); // I store the shop in the Session in my routes, you can do it a different way if you wish
// This will find the UUID of the Shop's keyset as long as the current user has access to this shop
Meteor.call('getKeyset', shop, function(error, shopKeyset){
if(!error) {
api = new Shopify.API({
shop: shop,
keyset: shopKeyset
});
Shopify.getEmbeddedAppAPI(function(err, ShopifyApp) {
if (err) {
console.log("Getting Embedded App SDK Failed");
return;
}
ShopifyApp.init({
apiKey: api_key,
shopOrigin: "https://" + shop + ".myshopify.com",
});
// Note: requests through API are proxied via Meteor Method.
api.countOrders(function(err, count) {
if (err) {
ShopifyApp.flashError("Counting orders failed: " + err);
} else {
ShopifyApp.flashNotice("There are " + count + " order(s)");
}
});
});
} else {
console.log("No credentials found");
}
});
// This code can be anywhere you want on the client, ie. in a route, in an event handler, etc
// You can have multiple authenticators on different pages/routes with different options if you need to
// Get the shop however you need to, in this case the query parameter.
const shop = query.shop.substring(0, query.shop.indexOf('.'));
var authenticator = new Shopify.PublicAppOAuthAuthenticator({
shop: shop,
api_key: 'API_KEY_HERE'
keyset: "auth", // auth is the keyset I generated in /server/startup.js that contains the API key & secret
scopes: "read_products",
embedded_app_sdk: true,
post_auth_uri: 'https://' + shop + '.myshopify.com/admin/apps/' + envAPIKey //this will send them to the embedded app after auth
});
window.top.location.href = authenticator.auth_uri;
// Subscribe to shopData to make Shops collection available to the client
Meteor.subscribe("shopData");
Shopify.harden(); // do not send accessToken to client, we only need it on the server
Shopify.onAuth(function(access_token, authConfig, userId) {
var shopUUID = uuid.new(); // Generate a unique ID for this shop so we can find it's corresponding keyset later
var existingShop = Shops.findOne({shop: authConfig.shop});
if(existingShop) {
shopUUID = existingShop.locator; //If the shop already exists, use it's existing unique ID instead
}
// Update or upsert the shop record
Shops.update({shop: authConfig.shop}, {
$set: {
locator: shopUUID // Save the unique ID value to the locator field so we can find it's corresponding keyset later
},
}, {upsert: true});
// Add the Keyset so it can be accessed by the server, use the unique ID instead of the shop name (for security)
Shopify.addKeyset(shopUUID, {
access_token: access_token
});
// Add a copy of the Keyset to the database so we can reload it into memory anytime the server restarts
Keysets.upsert({name: shopUUID}, {$set: { token: access_token }});
});
envAPIKey = process.env.SHOPIFYKEY;
envSecretKey = process.env.SHOPIFYSECRET;
Keysets = new Mongo.Collection("keysets"); // Server only collection to persist Keysets
// Publish the Shops collection to clients
Meteor.publish("shopData", function () {
return Shops.find({userId: this.userId}, {fields: {shop: 1, locator: 1}});
});
Meteor.startup(function () {
// add the auth keyset with secret. "adding" a keyset simply loads it into the servers memory.
// Since it is not persisted anywhere, we add it immediately at startup.
Shopify.addKeyset("auth", {
api_key: envAPIKey,
secret: envSecretKey
});
// Reload keysets into memory at startup from the Keysets collection so that they persist between reloads
existingKeysets = Keysets.find({});
existingKeysets.forEach(function(keyset) {
var thisName = keyset.name;
var thisToken = keyset.token;
Shopify.addKeyset(thisName, {
access_token: thisToken
});
});
});
// This is not very secure, you should find a way to make sure that the client requesting the Keyset UUID has permission
// This is partially why I use accounts, but if you can come up with a way to verify the request, you should add that check
// to the method and throw an error if it does not pass your check.
Meteor.methods({
getKeyset: function(shop) {
// Returns the keyset that corresponds to the this shop
var shopKeyset = Shops.findOne({shop: shop}).locator;
return shopKeyset;
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment