2017-03 Security Update

As a part of an ongoing effort to make Lightspeed Retail (X-Series) and its API as secure as possible, we have decided to introduce a couple of security-related changes. Both of these changes are introduced to reduce the risk of leaking sensitive information that could lead to compromising the safety of retailer's data.

IMPORTANT: This update will only affect a small number of retailers and integrators. The majority of our integrators are already using the correct methods which are not changing. The methods that are being deprecated are only used by a small fraction of applications.

1. OAuth flow

The second step of the OAuth flow involves exchanging the intermediate access code for a token. It was always recommended that the parameters required for that exchange are delivered as the body of the request. However, it was possible to include them as request parameters in the URL. After the change takes place on the 15th of April 2017, it will no longer be possible to use the URL params, and all the data will have to be delivered inside the body of the request.

2. Authorizing requests

It was always recommended in our documentation that the access token is delivered inside the Authorization header. However, for testing purposes, it was also possible to authorize an API request by submitting the token in the URL as below:

https://<<domain_prefix>>.retail.lightspeed.app/api/products?access_token=5PRfe98oqBZ0McC9jsf8PI:xsQyIL6mmrl0BafXM

This method is being deprecated and after the 15th of April 2017 it will only be possible to submit the token inside the header.

Code samples

We've prepared some code samples to illustrate the old, deprecated method and what the new code may look like.

OAuth flow - exchanging the code for the token

Deprecated:

curl -X GET "https://<<domain_prefix>>.retail.lightspeed.app/api/1.0/token?code={code}&client_id={client_id}&client_secret={client_secret}&grant_type=authorization_code&redirect_uri={redirect_uri}"

Correct:

  • cURL:
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'code={code}&client_id={client_id}&client_secret={client_secret}&grant_type=authorization_code&redirect_uri={redirect_uri}' "https://<<domain_prefix>>.retail.lightspeed.app/api/1.0/token"
  • PHP:
<?php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://<<domain_prefix>>.retail.lightspeed.app/api/1.0/token",
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS => "code={code}&client_id={client_id}&client_secret={client_secret}&grant_type=authorization_code&redirect_uri={redirect_uri}",
  CURLOPT_HTTPHEADER => array(
    "content-type: application/x-www-form-urlencoded",
  ),
));

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
  • C# (using RestSharp):
var client = new RestClient("https://<<domain_prefix>>.retail.lightspeed.app/api/1.0/token");
var request = new RestRequest(Method.POST);
request.AddHeader("content-type", "application/x-www-form-urlencoded");
request.AddParameter("application/x-www-form-urlencoded", "code={code}&client_id={client_id}&client_secret={client_secret}&grant_type=authorization_code&redirect_uri={redirect_uri}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
  • Ruby:
require 'uri'
require 'net/http'

url = URI("https://<<domain_prefix>>.retail.lightspeed.app/api/1.0/token")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["content-type"] = 'application/x-www-form-urlencoded'
request.body = "code={code}&client_id{client_id}&client_secret={client_secret}&grant_type=authorization_code&redirect_uri={redirect_uri}"

response = http.request(request)
puts response.read_body
  • Python:
import http.client

conn = http.client.HTTPSConnection("<<domain_prefix>>.retail.lightspeed.app")
payload = "code={code}&client_id={client_id}&client_secret={client_secret}&grant_type=authorization_code&redirect_uri={redirect_uri}"
headers = {
    'content-type': "application/x-www-form-urlencoded"
}
conn.request("POST", "/api/1.0/token", payload, headers)
res = conn.getresponse()
data = res.read()

print(data.decode("utf-8"))
  • Go:
package main

import (
    "fmt"
    "strings"
    "net/http"
    "io/ioutil"
)

func main() {

    url := "https://<<domain_prefix>>.retail.lightspeed.app/api/1.0/token"

    payload := strings.NewReader("code={code}&client_id={client_id}&client_secret={client_secret}&grant_type=authorization_code&redirect_uri={redirect_uri}")

    req, _ := http.NewRequest("POST", url, payload)
    req.Header.Add("content-type", "application/x-www-form-urlencoded")
    res, _ := http.DefaultClient.Do(req)
    defer res.Body.Close()
    body, _ := ioutil.ReadAll(res.Body)

    fmt.Println(res)
    fmt.Println(string(body))

}

Using the token to authorise the request

Deprecated:

curl -X GET -H "Content-Type: application/json" "https://<<domain_prefix>>.retail.lightspeed.app/api/products?access_token=5PRfe98oqBZ0McC9jsf8PI:xsQyIL6mmrl0BafXM"

Correct:

  • cURL:
curl -X GET -H "Content-Type: application/json" -H "Authorization: Bearer 5PRfe98oqBZ0McC9jsf8PI:xsQyIL6mmrl0BafXM" "https://<<domain_prefix>>.retail.lightspeed.app/api/products"
  • PHP:
<?php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://<<domain_prefix>>.retail.lightspeed.app/api/products",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_HTTPHEADER => array(
    "authorization: Bearer 5PRfe98oqBZ0McC9jsf8PI:xsQyIL6mmrl0BafXM",
    "content-type: application/json",
  ),
));

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
  • C# (using RestSharp):
var client = new RestClient("https://<<domain_prefix>>.retail.lightspeed.app/api/products");
var request = new RestRequest(Method.GET);
request.AddHeader("authorization", "Bearer 5PRfe98oqBZ0McC9jsf8PI:xsQyIL6mmrl0BafXM");
request.AddHeader("content-type", "application/json");
IRestResponse response = client.Execute(request);
  • Ruby:
require 'uri'
require 'net/http'

url = URI("https://<<domain_prefix>>.retail.lightspeed.app/api/products")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["content-type"] = 'application/json'
request["authorization"] = 'Bearer 5PRfe98oqBZ0McC9jsf8PI:xsQyIL6mmrl0BafXM'

response = http.request(request)
puts response.read_body
  • Python:
import http.client

conn = http.client.HTTPSConnection("<<domain_prefix>>.retail.lightspeed.app")
headers =
{
    'content-type': "application/json",
    'authorization': "Bearer 5PRfe98oqBZ0McC9jsf8PI:xsQyIL6mmrl0BafXM",
    }

conn.request("GET", "/api/products", payload, headers)
res = conn.getresponse()
data = res.read()

print(data.decode("utf-8"))
  • Go:
package main

import (
    "fmt"
    "strings"
    "net/http"
    "io/ioutil"
)

func main() {

    url := "https://<<domain_prefix>>.retail.lightspeed.app/api/products"

    req, _ := http.NewRequest("GET", url, nil)

    req.Header.Add("content-type", "application/json")
    req.Header.Add("authorization", "Bearer 5PRfe98oqBZ0McC9jsf8PI:xsQyIL6mmrl0BafXM")
    res, _ := http.DefaultClient.Do(req)
    defer res.Body.Close()
    body, _ := ioutil.ReadAll(res.Body)

    fmt.Println(res)
    fmt.Println(string(body))

}