Giter Site home page Giter Site logo

shopify-token's Introduction

shopify-token

Version npm Build Status Coverage Status

This module helps you retrieve an access token for the Shopify REST API. It provides some convenience methods that can be used when implementing the OAuth 2.0 flow. No assumptions are made about your server-side architecture, allowing the module to easily adapt to any setup.

Install

npm install --save shopify-token

API

The module exports a class whose constructor takes an options object.

new ShopifyToken(options)

Creates a new ShopifyToken instance.

Arguments

  • options - A plain JavaScript object, e.g. { apiKey: 'YOUR_API_KEY' }.

Options

  • apiKey - Required - A string that specifies the API key of your app.
  • sharedSecret - Required - A string that specifies the shared secret of your app.
  • redirectUri - Required - A string that specifies the URL where you want to redirect the users after they authorize the app.
  • scopes - Optional - An array of strings or a comma-separated string that specifies the list of scopes e.g. 'read_content,read_themes'. Defaults to 'read_content'.
  • timeout - Optional - A number that specifies the milliseconds to wait for the server to send a response to the HTTPS request initiated by the getAccessToken method before aborting it. Defaults to 60000, or 1 minute.
  • accessMode - Optional - A string representing the API access modes. Set this option to 'per-user' to receive an access token that respects the user's permission level when making API requests (called online access). This is strongly recommended for embedded apps. Defaults to offline access mode.
  • agent - Optional - An HTTPS agent which will be passed to the HTTPS request made for obtaining the auth token. This is useful when trying to obtain a token from a server that has restrictions on internet access.

Return value

A ShopifyToken instance.

Exceptions

Throws a Error exception if the required options are missing.

Example

const ShopifyToken = require('shopify-token');

const shopifyToken = new ShopifyToken({
  sharedSecret: '8ceb18e8ca581aee7cad1ddd3991610b',
  redirectUri: 'http://localhost:8080/callback',
  apiKey: 'e74d25b9a6f2b15f2836c954ea8c1711'
});

shopifyToken.generateNonce()

Generates a random nonce.

Return value

A string representing the nonce.

Example

const nonce = shopifyToken.generateNonce();

console.log(nonce);
// => 212a8b839860d1aefb258aaffcdbd63f

shopifyToken.generateAuthUrl(shop[, scopes[, nonce[, accessMode]]])

Builds and returns the authorization URL where you should redirect the user.

Arguments

  • shop - A string that specifies the name of the user's shop.
  • scopes - An optional array of strings or comma-separated string to specify the list of scopes. This allows you to override the default scopes.
  • nonce - An optional string representing the nonce. If not provided it will be generated automatically.
  • accessMode - An optional string dictating the API access mode. If not provided the access mode defined by the accessMode constructor option will be used.

Return value

A string representing the URL where the user should be redirected.

Example

const url = shopifyToken.generateAuthUrl('dolciumi');

console.log(url);
// => https://dolciumi.myshopify.com/admin/oauth/authorize?scope=read_content&state=7194ee27dd47ac9efb0ad04e93750e64&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fcallback&client_id=e74d25b9a6f2b15f2836c954ea8c1711

shopifyToken.verifyHmac(query)

Every request or redirect from Shopify to the client server includes a hmac parameter that can be used to ensure that it came from Shopify. This method validates the hmac parameter.

Arguments

  • query - The parsed query string object.

Return value

true if the hmac is valid, else false.

Example

const ok = shopifyToken.verifyHmac({
  hmac: 'd1c59b480761bdabf7ee7eb2c09a3d84e71b1d37991bc2872bea8a4c43f8b2b3',
  signature: '184559898f5bbd1301606e7919c6e67f',
  state: 'b77827e928ee8eee614b5808d3276c8a',
  code: '4d732838ad8c22cd1d2dd96f8a403fb7',
  shop: 'dolciumi.myshopify.com',
  timestamp: '1452342558'
});

console.log(ok);
// => true

shopifyToken.getAccessToken(hostname, code)

Exchanges the authorization code for a permanent access token.

Arguments

  • hostname - A string that specifies the hostname of the user's shop. e.g. foo.myshopify.com. You can get this from the shop parameter passed by Shopify in the confirmation redirect.
  • code - The authorization Code. You can get this from the code parameter passed by Shopify in the confirmation redirect.

Return value

A Promise which gets resolved with an access token and additional data. When the exchange fails, you can read the HTTPS response status code and body from the statusCode and responseBody properties which are added to the error object.

Example

const code = '4d732838ad8c22cd1d2dd96f8a403fb7';
const hostname = 'dolciumi.myshopify.com';

shopifyToken
  .getAccessToken(hostname, code)
  .then((data) => {
    console.log(data);
    // => { access_token: 'f85632530bf277ec9ac6f649fc327f17', scope: 'read_content' }
  })
  .catch((err) => console.err(err));

License

MIT

shopify-token's People

Contributors

corbinu avatar lpinca avatar olliechick avatar sinha-sahil avatar timolins avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

shopify-token's Issues

Shared Secret

Should sharedSecret be the secret key from Shopify or something I make up myself?

Online and Offline Access Tokens

Hello. I've got a use-case where I require both an offline and online access token. An offline access token for performing background tasks that interact with the Shopify store, and an online access token so I can get details of the logged in user to display within a timeline of events for my Shopify app.

Using your library, what would be the best way of achieving this?

Would you suggest performing 2 Oauth flows, first for the offline access token (save it), then immediately after another for the online access token (save and check expiry on each page load)? Do you have an example of how this would work or is there some other way I have to tackle this requirement?

Thanks.

generateAuthUrl

I am not sure if this works as intended after the application is already installed, since it automatically injects myshopify.com

return url.format({
      pathname: '/admin/oauth/authorize',
      hostname: `${shop}.myshopify.com`,
      protocol: 'https:',
      query
    });

My first round was fine, but after the app is installed and the link is present in the user's dashboard, I think it keeps adding myshopify.com resulting in some wonky urls like:

made2measure-example.myshopify.com.myshopify.com

It actually took me a couple of hours to finally address this, which I did with:

if (uri.match(/.myshopify.com/gi).length > 0) {
      uri.replace('.myshopify.com', '')
    }

Verification fails when using arrays

Hello,

Is there a reason why the verification would fail if there are arrays in the request. For example if you would want to verify the Action Drop Down (that contains multiple ids of products etc)?

Kasimir

Add Support for Online Access Mode

Currently this only supports offline access mode (https://help.shopify.com/en/api/getting-started/authentication/oauth/api-access-modes). We should make it configurable so that an app could specify online or offline access mode with a default of offline (current).

Looks like we explicitly need to pass &grant_options[]={access_mode} as it "defaults to offline access mode if left blank or omitted. Set to per-user for online access mode."

I'll see if I can take a stab as I need this for an app I'm developing. Need to add it to the query: https://github.com/lpinca/shopify-token/blob/master/index.js#L84. In addition, extra data is returned:

{
  "access_token": "f85632530bf277ec9ac6f649fc327f17",
  "scope": "write_orders,read_customers",
  "expires_in": 86399,
  "associated_user_scope": "write_orders",
  "associated_user": {
    "id": 902541635,
    "first_name": "John",
    "last_name": "Smith",
    "email": "[email protected]",
    "account_owner": true,
    "locale": "en",
    "collaborator": false
  }
}

Not sure about the best way in returning this additional information without a breaking change since we currently only return the access_token: https://github.com/lpinca/shopify-token/blob/master/index.js#L183. I'd prefer just returning an object of all data, maybe an extra parameter defaulted to false. getAccessToken(shop, code, allData = false) {

Can generateAuthUrl use a myshopify domain instead of the shop name

Based on the spec from https://help.shopify.com/en/api/getting-started/authentication/oauth#update-oauth-scopes, the auth url should be "the users myshopify domain".

However, the generateAuthUrl function appends ".myshopify.com". So during the authentication flow that Shopify describes they will send the myshopify domain. Then I have to remove the ".myshopify.com" from that parameter to be able to pass it to generateAuthUrl to work properly.

Can this be fixed? How do you use generateAuthUrl?

Anyway to pass metafields?

So we use wildcard domains: junaid.myapp.com. Every user gets a specific domain and can add multiple Shopify shops to their account. The issue is each redirect URL will be different. Can we send the subdomain as a metafield during oAuth and get Shopify to send it back during the callback? That way, we can have one redirect URL where we get the shop, accessToken, and the subdomain we sent during the initial call.

I also tried adding wildcard domains on the app settings page: *.myapp.com but seems like wildcards do not work.

Verify webhook

Could verifyHmac be extended to support the body of the webhook request after body parser had taken effect?

Current solution

    const hmac = request.headers['x-shopify-hmac-sha256']

    const bodyMessage = JSON.stringify(request.body)
    .replace('/', '\\/')
    .replace('&', '\\u0026')

    var calculatedSignature = crypto.createHmac('sha256', shopifyApp.secret)
    .update(bodyMessage.toString('utf8'))
    .digest('base64')

    const ok = hmac === calculatedSignature

Implement `timeout` option

It would be good for the getAccessToken method to allow a timeout via an options.timeout in the client constructor.

I've tried to do this on a branch of our fork and run into some trouble. (I'll post that momentarily.) Basically the low-level https.request interface that the module uses makes timeouts tricky to implement โ€“ there is a request.setTimeout method but I'm not sure how to fully implement it.

I wonder if this module should switch to using a request abstraction layer. shopify-api-node, for example, is using the got module; another obvious one is request. It would expand the size of this module but also open up a lot of possibilities for request options (such as timeout) without having to reinvent the wheel.

Preflight Cors Fails

Hello,

Thank you for creating this helpful library! I've been tinkering with it in a few tests apps to great success. Alas, I decided to come back to this library and build a new app, but I had unexpected behaviour:

Fetch API cannot load https://fabrics-2017.myshopify.com/admin/oauth/access_token. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access. The response had HTTP status code 404. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

My application code looks closer to this:...

    const query = queryString.parse(this.props.location.search);
    const checkPassed = shopifyToken.verifyHmac(query);
    console.log(query);
    if (checkPassed) {
      this.setState({
        securityChecksPassed: checkPassed,
      }, () => {
        shopifyToken.getAccessToken(query.shop, query.code)
          .then((token) => {
            console.log('got token', token);
          })
          .catch((err) => {
            console.log('err', err);
          })

Is this is an issue on my side? What am I missing?

verifyHmac always returns false

I'm trying to verify the hmac being returned from shopify after oauth but the verifyHmac method is always returning false. I'm testing this locally and trying to do this all on the front-end in react. When a user clicks "Add" button, the auth flow kicks off and should grab the permanent token to store on the backend. Not sure if theres something I'm missing here?

package.json
"shopify-token": "^4.0.4"

broken up query params after success oauth redirect (no signature provided in url):

url = "http://localhost:3000/integrations?code=41b132e6a83baa6d813353959e6d0aed&hmac=eef3f5e02951e6d46335524993a53611cb4062b016b5b4c2cbcb00f2df1e9db7&host=Z3JlYXN5LWhhbmRtYWRlLWNvLWRldi5teXNob3BpZnkuY29tL2FkbWlu&shop=dev-shop.myshopify.com&state=7dcaee1e8970ce0c3f291a591595925c&timestamp=1647547764"

query = {
    code: "41b132e6a83baa6d813353959e6d0aed"
    hmac: "eef3f5e02951e6d46335524993a53611cb4062b016b5b4c2cbcb00f2df1e9db7"
    shop: "dev-shop.myshopify.com"
    signature: undefined
    state: "7dcaee1e8970ce0c3f291a591595925c"
    timestamp: "1647547764"
}

Call verifyHmac() with query

const isLegit = shopifyToken.verifyHmac(query)
// isLegit = False

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.