web
You’re offline. This is a read only version of the page.
close
Support Portal

LiDAR: Challenge & Response and Session Token Authentication

Explains how to authenticate POST requests and access endpoints on devices using Challenge-Response and Session Token, including JSON examples.
Related Products
picoScan100

Table of Contents

Firmware Always use the latest available firmware. Check the SICK product page for the latest release.
OpenAPI Endpoint reference and full JSON schema are shipped in the OpenAPI specification for your device.
Overview

1 – Overview

SICK LiDAR sensors (picoScan100, multiScan100, LRS4000, …) expose a JSON-based REST API over plain HTTP on port 80. GET requests read parameters without authentication. POST requests that write a value or call a method always require authentication.

Two mechanisms work together. You need to understand both before you can use any authenticated endpoint:

Mechanism What it is Where it goes Typical use
Challenge-Response A one-time puzzle solved with a SHA-256 hash chain. Proves you know the password without sending it. JSON field "header" in the POST request body Writing any parameter (LocationName, etc.), creating a session token, running methods
Session Token A short-lived bearer token obtained after a successful challenge-response. HTTP request header X-Session-Token Binary file transfer endpoints (GET /api/parameterbackup, PUT /api/parameterbackup)
Rule of thumb If you are writing a JSON variable, use Challenge-Response only. If you are downloading or uploading a binary file, get a session token first and put it in the HTTP header.

Concept A

2 – Challenge-Response

Instead of a username and password, every protected POST carries a cryptographic proof inside its JSON body. Fetch a fresh challenge from the device before each request.

How it works

Client                                           Sensor
  |                                               |
  |  POST /api/getChallenge                       |
  |  { "data": { "user": "Service" } }            |
  |─────────────────────────────────────────────► |
  |                                               |
  |  { "challenge": { nonce, opaque,              |
  |                   realm, salt } }             |
  | ◄─────────────────────────────────────────────|
  |                                               |
  |  compute SHA-256 hash chain (see §6)          |
  |                                               |
  |  POST /api/<endpoint>                         |
  |  { "header": { nonce, opaque, realm,          |
  |                response, user },              |
  |    "data":   { ... } }                        |
  |─────────────────────────────────────────────► |
  |                                               |
  |  { "header": { "status": 0, "message": "Ok" }, ... }
  | ◄─────────────────────────────────────────────|

The challenge fields

POST /api/getChallenge returns a JSON object with four fields you must pass back in every authenticated request:

FieldTypeMeaning
noncestring (hex)One-time random value. Ties the response to this exact exchange.
opaquestring (hex)Server-side session handle. Must be echoed back unchanged.
realmstringAuthentication realm (e.g. "SICK Sensor"). Used in the hash.
saltarray of integers (uint8)Optional additional entropy appended to the password before hashing. May be all zeros but must still be included if the array is non-empty.

The authentication header

The computed proof travels in the "header" field of the POST body, not as an HTTP header. Its structure mirrors the challenge exactly, with one new field added:

{
  "header": {
    "nonce":    "<echo from challenge>",
    "opaque":   "<echo from challenge>",
    "realm":    "<echo from challenge>",
    "user":     "Service",
    "response": "<computed SHA-256 hex string>"
  }
}
Fetch a fresh challenge before each POST Call getChallenge immediately before every authenticated POST. The recommended practice is one challenge per request.

Concept B

3 – Session Token

A session token is a short-lived string that you can pass as a plain HTTP request header. It is used by endpoints that transfer binary data, where a JSON body containing an authentication header is not practical.

How to obtain a token

Call POST /api/CreateSessionToken using the normal challenge-response flow. The response body contains the token in data.token:

// Request (after computing auth header from a fresh challenge)
POST /api/CreateSessionToken
{
  "header": { "nonce": "...", "opaque": "...",
              "realm": "...", "user": "Service", "response": "..." }
}

// Response
{
  "header": { "status": 0, "message": "Ok" },
  "data": { "token": "dKVwNYUkHsHS..." }
}

How to use the token

Pass the token as an HTTP request header in subsequent calls to binary endpoints. No JSON body is required for these GETs or PUTs:

GET /api/parameterbackup HTTP/1.1
Host: 192.168.0.1
X-Session-Token: dKVwNYUkHsHS...

Token lifetime

PropertyValue
ScopeBound to the user level used during CreateSessionToken
LifetimeShort-lived; in testing the token remained valid for over 12 minutes without expiring
ReuseCan be used for multiple requests until it expires
Without tokenBinary endpoints return HTTP 403
Challenge-Response vs. Session Token at a glance
Challenge-Response is embedded in the "header" field of every POST body. A session token is an HTTP header you set once for binary transfer requests. They serve different purposes and are often used together: you do Challenge-Response first to create the token, then use the token for file operations.

Scenario 1

4 – Write a parameter: LocationName

Writing any JSON variable requires Challenge-Response only. No session token is needed. The example below shows the complete request/response sequence for LocationName. The same pattern applies to every other writable parameter.

1
Read current value (no auth)

GET /api/LocationName (no authentication required)

// Response
{
  "header": { "status": 0, "message": "Ok" },
  "data": { "LocationName": "picoScan150 25420067" }
}
2
Fetch a challenge
// Request
POST /api/getChallenge
{ "data": { "user": "Service" } }

// Response
{
  "header": { "status": 0, "message": "Ok" },
  "challenge": {
    "nonce":  "aa8d5559c2ef6b7a9c29...",
    "opaque": "bfecc58bf27d5312a6fa...",
    "realm":  "SICK Sensor",
    "salt":   [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  }
}

Note: challenge is at the top level of the response, not wrapped inside a "data" field.

3
Compute the response hash (see §6)
hash1    = SHA256( "Service:SICK Sensor:servicelevel:" + raw_salt_bytes )
hash2    = SHA256( "POST:LocationName" )
response = SHA256( hash1 + ":" + nonce + ":" + hash2 )
4
Write the new value
// Request
POST /api/LocationName
{
  "header": {
    "nonce":    "aa8d5559c2ef6b7a9c29...",
    "opaque":   "bfecc58bf27d5312a6fa...",
    "realm":    "SICK Sensor",
    "user":     "Service",
    "response": "<computed hex string>"
  },
  "data": { "LocationName": "my-sensor" }
}

// Response
{
  "header": { "status": 0, "message": "Ok" }
}

The data payload key ("LocationName") must match the endpoint name exactly, including capitalisation.


Scenario 2

5 – Download a config backup: parameterbackup

The backup download uses both mechanisms. Challenge-Response is used to create a session token and to trigger the backup creation. The session token is then used for the actual file download.

Order matters: you must create the token before triggering the backup, because the backup creation is asynchronous and you will need the token ready when it completes.

1
Create a session token (Challenge-Response)
// Fetch challenge first (as shown in §4, step 2), then:

POST /api/CreateSessionToken
{
  "header": { "nonce": "...", "opaque": "...",
              "realm": "SICK Sensor", "user": "Service",
              "response": "<computed>" }
}

// Response
{
  "header": { "status": 0, "message": "Ok" },
  "data": { "token": "dKVwNYUkHsHS..." }
}
2
Trigger backup creation (Challenge-Response)

This is asynchronous. Fetch a new challenge (the previous nonce is now consumed) and send:

POST /api/CreateParameterBackup
{
  "header": { "nonce": "...", "opaque": "...",
              "realm": "SICK Sensor", "user": "Service",
              "response": "<computed for endpoint 'CreateParameterBackup'>" },
  "data": { "Passphrase": "test" }
}

// Response
{ "header": { "status": 0, "message": "Ok" } }

// Wait for the backup to finish before downloading (see note below).
3
Download the backup (session token)

Use the token from step 1 in an HTTP header:

// Request
GET /api/parameterbackup HTTP/1.1
Host: 192.168.0.1
X-Session-Token: dKVwNYUkHsHS...

// Response: HTTP 200, binary backup file (≈86 KB)
// Without the header: HTTP 403 Forbidden
4
Restore (optional): upload the backup
// Upload (same session token, binary body)
PUT /api/parameterbackup HTTP/1.1
Host: 192.168.0.1
X-Session-Token: dKVwNYUkHsHS...
Content-Type: application/octet-stream

<binary backup content>

// Then activate the restored parameters (Challenge-Response):
POST /api/RestoreParameterBackup
{
  "header": { ... },
  "data": { "Passphrase": "test" }
}
// Wait ~1 second, then optionally persist:
POST /api/PersistParameters  { "header": { ... } }
Async operations require a delay CreateParameterBackup and RestoreParameterBackup run asynchronously on the device. Give the device a moment to complete the operation before attempting to read or upload the file.

Hash Computation

6 – Computing the response hash

All values are hex-encoded SHA-256 digests of UTF-8 strings (or raw bytes in the case of salt).

// Inputs
user     = "Service"
password = "servicelevel"
realm    = challenge["realm"]        // e.g. "SICK Sensor"
nonce    = challenge["nonce"]        // hex string
salt     = bytes(challenge["salt"])  // JSON uint8 array → raw bytes

// Step 1: hash1
base1 = user + ":" + realm + ":" + password   // all UTF-8
if len(salt) > 0:
    base1 = base1 + ":" + salt   // literal colon then raw salt bytes
hash1 = SHA256(base1)            // returns lowercase hex string

// Step 2: hash2
hash2 = SHA256("POST:" + endpoint_name)   // e.g. "POST:LocationName"

// Step 3: response
response = SHA256(hash1 + ":" + nonce + ":" + hash2)

// Use response in the "header" object of your POST body

Notes on the salt

The salt field in the challenge is a JSON array of unsigned 8-bit integers. Decode it to raw bytes before appending. If the array is non-empty (even if all bytes are zero), the salt must be appended to base1. An all-zero salt is not the same as no salt.

Example: the picoScan150 at factory defaults returns [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] (16 zero bytes). This array is non-empty, so it must be included in the hash.

Python pseudo-code (verified)

import hashlib, requests

def sha256(b: bytes) -> str:
    return hashlib.sha256(b).hexdigest()

def get_challenge(device_ip: str, user: str) -> dict:
    r = requests.post(
        f"http://{device_ip}/api/getChallenge",
        json={"data": {"user": user}}
    )
    return r.json()["challenge"]   # top-level, not inside "data"

def compute_response(user, password, challenge, endpoint):
    realm  = challenge["realm"]
    nonce  = challenge["nonce"]
    salt   = bytes(challenge["salt"])   # uint8 array → raw bytes
    base1  = (user + ":" + realm + ":" + password).encode("utf-8")
    if salt:
        base1 = base1 + b":" + salt
    hash1    = sha256(base1)
    hash2    = sha256(("POST:" + endpoint).encode("utf-8"))
    return sha256((hash1 + ":" + nonce + ":" + hash2).encode("utf-8"))

def auth_post(device_ip, user, password, endpoint, data=None):
    c    = get_challenge(device_ip, user)
    body = {
        "header": {
            "nonce":    c["nonce"],
            "opaque":   c["opaque"],
            "realm":    c["realm"],
            "user":     user,
            "response": compute_response(user, password, c, endpoint)
        }
    }
    if data:
        body["data"] = data
    return requests.post(f"http://{device_ip}/api/{endpoint}", json=body)
Hash chain summary
hash1 = SHA256(user + “:” + realm + “:” + password [+ “:” + salt_bytes])
hash2 = SHA256(“POST:” + endpointName)
response = SHA256(hash1 + “:” + nonce + “:” + hash2)
Keywords:
SICK CROWN, REST API, Challenge-Response, Session Token, SHA-256, authentication, picoScan, multiScan, LRS4000, parameter backup, getChallenge, CreateSessionToken, X-Session-Token, HTTP POST, JSON