All Tutorials

Authenticate an extension built with Argo

All Tutorials

Authenticate an extension built with Argo

Authenticate an extension built with Argo

Argo apps use session tokens to authenticate requests between the extension and your backend server. Session tokens are secure packets of data about a merchant session in the Shopify Admin, similiar to a cookie. A session token provides the information required to validate that a request is coming from Shopify, and also provides the IDs of the user and shop.

How it works

When your extension built with Argo loads in the Shopify Admin, you can use the session token API to fetch the session token. After you have the token, include it with all of the requests you make to your backend server. The token is signed using a shared secret so that your backend can verify if the request is valid.

Make a request to your app’s server using a session token

1. Configure your backend server

Extensions built using Argo are hosted on Shopify's servers. To receive requests from your extension, you need to enable cross-domain requests on your backend server.

If you're using Rails, then add the following code in config/application.rb to enable cross-domain requests:

## config/application.rb ##
Rails.application.config.middleware.insert_before(0, Rack::Cors) do
  allow do
    origins '*' # allow access to this api from any domain
    resource '*', # allow all origins access to all resources
      headers: ['authorization', 'content-type', 'context'], # restrict headers to relevant keys
      methods: [:post] # restrict the methods to only the ones expected to be used by the extension
  end
end

2. Fetch a session token from the Shopify Admin

In your extension, use the session token API to fetch a new session token. Session tokens expire every minute, so always fetch a new token before making a request to your backend server.

Javascript:

import {extend, TextField} from '@shopify/argo-admin';
function MyExtension(root, api) { const sessionToken = api.sessionToken;
const text = root.createComponent(TextField, { disabled: true, value: '', label: 'Session Token', });
sessionToken.getSessionToken().then((newToken) => { text.updateProps({ value: newToken, }); });
root.appendChild(text); root.mount(); }
extend('MyExtensionPoint', MyExtension);

React:

import {TextField} from '@shopify/argo-admin';
import {extend, render, useSessionToken} from '@shopify/argo-admin-react';
function App() { const {getSessionToken} = useSessionToken(); const [token, setToken] = useState('');
useEffect(() => { getSessionToken().then((newToken) => { setToken(newToken); }); }, []); return <TextField label="Session Token" value={token} disabled />; } extend('MyExtension', render(() => <App />));

3. Make a request to your backend server

After your extension has received a session token, you can include it in requests to your server. How you include the session token in the request is up to you, but we suggest including it in the request header as follows:

Javascript:

import {extend, Button} from '@shopify/argo-admin';
extend('MyExtension', (api, root) => { const sessionToken = api.sessionToken;
const sendRequest = async () => { const token = await sessionToken.getSessionToken(); const response = await fetch('https://server-url-here', { headers: { 'any-header-key': token || 'unknown token', }, }); console.log('Response', response.text()); };
const button = root.createComponent(Button, { title: 'Send request', onClick: sendRequest, }); root.append(button); });

React:

import React, {useCallback} from 'react';
import {extend, render, useSessionToken, Button} from '@shopify/argo-admin-react';
function App() { const {getSessionToken} = useSessionToken();
const sendRequest = useCallback(async () => { const token = await getSessionToken(); const response = await fetch('https://server-url-here', { headers: { 'any-header-key': token || 'unknown token', }, }); console.log('Response', response.text()); }, [getSessionToken]);
return <Button title="Send request" onClick={sendRequest} />; }
extend('MyExtension', render(() => <App />));

Receive a request and decode the session token

When your server receives a request from your extension that includes a session token, you need to decode it. Session tokens use the JSON Web Token (JWT) format. Libraries to encode and decode JWT are available for all common server frameworks.

Pass the following parameters to the JWT decode method:

  • the session token received in your extension's request
  • the api secret key for your app
  • the HS256 algorithm.

Ruby example:

JWT.decode(token_sent_from_the_extension, app_api_secret_key, true,"HS256")

The session token contains information about the current shop and user, including the shop's domain, your app’s API key, and the user ID. You can use this information to establish per-user sessions, similar to an authentication cookie.

Structure of a session token JWT

A decoded Shopify session token contains the following fields:

Header:

{
  "alg": "HS256",  -- The algorithm used to encode the JWT
  "typ": "JWT",    -- https://tools.ietf.org/html/rfc7519#section-5.1
}

Payload:

{
  "iss": "<shop-name.myshopify.com/admin>", -- The shop’s admin domain
  "dest": "<shop-name.myshopify.com>",      -- The shop’s domain
  "aud": "<api key>",         -- The Api key of the receiving app
  "sub": "<user id>",         -- The user the JWT is intended for
  "exp": "<time in seconds>",   -- when the JWT expires
  "nbf": "<time in seconds>",   -- when the JWT activates
  "iat": "<time in seconds>",   -- when the JWT was issued
  "jti": "<random UUID>",   -- A secure random UUID
}

Sample payload:

{
 "iss"=>"https://exampleshop.myshopify.com/admin",
 "dest"=>"https://exampleshop.myshopify.com",
 "aud"=>"api-key-123",
 "sub"=>42,
 "exp"=>1591765058,
 "nbf"=>1591764998,
 "iat"=>1591764998,
 "jti"=>"f8912129-1af6-4cad-9ca3-76b0f7621087"
}

Validate a session token

To validate a session token, you can manually validate session tokens, use the shopify app gem, or configure a Rails app to perform the validation.

Manually validate a session token

To verify that a session token is coming from Shopify, you can encode a new session token using your app’s shared secret and compare it to the encoded token received from your extension.

An encoded JSON Web Token (JWT) is a string with the following structure (all three sections are base64 encoded):

{header}.{payload}.{signature}

The token header and payload are documented in the previous section. The signature verifies that the header and payload were encoded using the shared secret, confirming that the token was generated by Shopify.

To validate a session token manually:

  1. Hash the decoded <header> and <payload> values from the extension session token using the SHA-256 algorithm.

  2. Sign the string using JWT, specifying the HS256 algorithm and using the app’s secret as the signing key.

  3. Base64url-encode the result.

  4. Compare the new session token with the session token received from your extension. If the session tokens are identical, then the request came from Shopify.

Use the shopify app gem to validate a session token

If your server uses the shopify app gem to validate session authentication for GraphQL requests, then requests containing a valid JWT token in the request header will be automatically be authorized. An error is raised if an invalid token is received.

The next steps in this section can be skipped if any of the following criteria are met:

  • The app uses shopify app gem version 13.1.0 or newer.
  • The server doesn’t use Rails.
  • The server is already configured to process GraphQL requests using the shopify app gem.

Configure a Rails app to validate a session token

  1. Add the following route to config/routes.rb:
post "/graphql_extension", to: "graphql#execute_extension"
  1. Replace the contents of app/controllers/graphql_controller.rb with the following code:
# frozen_string_literal: true

# ... existing imports ...
class GraphqlController < AuthenticatedController
  class UnauthorizedRequest < RuntimeError; end

  # If accessing from outside this domain, nullify the session
  # This allows for outside API access while preventing CSRF attacks,
  # but you'll have to authenticate your user separately
  # protect_from_forgery with: :null_session
  protect_from_forgery with: :null_session, prepend: true
  before_action :validate_request, only: [:execute_extension], prepend: true

  ENCODING_ALGORITHM = "HS256"

  # ... existing public methods

  def execute_extension
    puts "---DEBUG---: JWT VALIDATION SUCCESS!"
    # ... execute on server schema or proxy to Shopify
  end

  private

  def validate_request
    # JWT.decode(token, secret, verify, algorithm)
    #   -> takes the secret, runs it over the encoding algo, compares it with the token from the client
    JWT.decode(jwt_token_from_request, 'custom-fields-dev-secret', true, algorithm: ENCODING_ALGORITHM)
  rescue JWT::DecodeError
    raise UnauthorizedRequest
  end

  def jwt_token_from_request
    token = (/Bearer (?<token>.+)/.match(request.headers.fetch("authorization", "")) || {})[:token]
    raise UnauthorizedRequest unless token.present?
    token
  end

  # ... existing private methods
end

Making calls to Shopify’s Admin API

To communicate with the Shopify Admin API, your extension must make requests through your backend server. You need to build an API that your extension can call, which in turn calls the Shopify Admin API.

Next steps