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

Authenticated REST Communication via Postman

A step-by-step guide to configuring Postman to automatically perform SICK Challenge-Response authentication.

Table of Contents

Demonstration use The pre-request script in this guide is provided for demonstration and evaluation. It is not hardened for production use. Review, test, and adapt it to your own security requirements before using it in a productive environment.
Overview

1 – What you are building

SICK LiDAR sensors expose a JSON REST API over plain HTTP. Reading a value (GET) needs no authentication. Writing a value or running a method (POST) requires Challenge-Response authentication: the device hands out a one-time puzzle, and the client answers with a SHA-256 hash chain that proves it knows the password, without ever sending the password.

Doing that by hand for every request is tedious. This guide sets up Postman so it happens automatically: you paste a small script once at the collection level, and every protected POST is authenticated for you.

What you need

ItemDetail
Postman desktop appFree. No paid plan required. A free account is needed – without signing in, some features (e.g. Import) are unavailable. Windows / macOS / Linux. Tested with Postman v12.21.2.
Network access to the sensorYour PC must reach the device IP (default 192.168.0.1).
OpenAPI file for your deviceThe openapi.yaml that ships with the sensor / firmware. Download it from the SICK product page under Software.
Device credentialsA user level (e.g. Service) and its password.
Firmware Always use the latest available firmware. Check the SICK product page for the latest release.

2 – Install and open Postman

1
Download the free desktop app

Get it from postman.com/downloads and install it like any other application.

2
Sign in with a free account

Create or sign in with a free Postman account. Testing shows that without an account some features (such as Import) are not available. A free account is enough – no paid plan or Postman cloud subscription is required.

3
Learn the layout

Left sidebar: Collections (your requests) and Environments (your variables). Center: the request you are editing. Bottom: the Console (View → Show Postman Console). You will use it to confirm the authentication is running.


3 – Import the OpenAPI file to get a collection

Postman can turn the device’s openapi.yaml into a ready-made collection: a folder of pre-built requests for every endpoint, so you do not have to type URLs by hand.

Where to get the OpenAPI file Download it from the sensor’s product page on the SICK website, in the Software area (alongside firmware and documentation). Save the openapi.yaml locally before importing.
1
Click Import

At the top of the left sidebar, click the Import icon (next to the + / new-tab button).

2
Choose the OpenAPI file

Drag your openapi.yaml into the window, or click files and select it. Postman detects the OpenAPI format automatically.

3
Import as a Collection

When prompted, keep the default that generates an API collection (a “Postman Collection”). Confirm the import.

4
Find your requests

A new collection (e.g. “picoScan150 REST interface description”) appears under Collections. Expand it to see folders such as LocationName, EtherIPAddress, … each containing ready-made GET and POST requests.

Base URL as a variable OpenAPI imports usually reference the server as {{baseUrl}}. That is a variable you will fill in next. If your requests instead use a literal IP, that is fine too. You can still follow the rest of this guide.

4 – Create the environment and enter your password

An environment is a named set of variables (device IP, user, password). Keeping them here means you never hard-code them into requests and can switch between devices easily.

1
Create an environment

Left sidebar → Environments+. Name it e.g. SICK REST Auth.

2
Add these variables
VariableExample valueNotes
baseUrlhttp://192.168.0.1/apiScheme + host. Include /api here if your requests are written as {{baseUrl}}/LocationName.
userServiceThe device user level.
password(enter manually)Optionally Mark as sensitive. See step 3.
enableAuthenticationtrueMaster switch. Set false to send requests without auth.
challengePath/getChallengeOptional. Where the challenge endpoint lives, relative to baseUrl. With /api already in baseUrl, use /getChallenge.
3
Enter the password manually

In the password row, type your device password into the Current value column. You can optionally Mark as sensitive to mask it in the UI.

The Current value stays on your machine only: it is not exported, not synced, and not shared when you share the collection. Leave “Initial value” blank so no password is ever written to a shareable file.

4
Activate the environment

Use the environment selector at the top-right of Postman and choose SICK REST Auth. If it shows “No environment”, your {{baseUrl}} will not resolve and requests will fail.

Never commit real credentials Keep the password in the Current value field only. Do not paste it into the script, a request body, or any file you export or check into version control.

5 – Paste the pre-request script at collection level

A pre-request script runs automatically before a request is sent. Placed at the collection level, it applies to every request in that collection, so all protected POSTs get authenticated with one setup.

1
Open the collection editor

Click the collection name in the left sidebar to open it.

2
Go to Scripts → Pre-request

Open the Scripts tab, then the Pre-request sub-tab. (In current Postman this is the “Scripts” tab; older versions called it “Pre-request Script”.)

3
Paste the script below and Save

Copy the entire script from §8, paste it in, and click Save. That is the only code you need. It uses the built-in CryptoJS library, so there is nothing to install.

New collection = paste again The script lives inside one collection. If you re-import the OpenAPI file or create a new collection, paste the script into that collection’s Pre-request tab too.

6 – Send your first authenticated write

Let’s change LocationName. Writing a parameter needs Challenge-Response only. The script handles it.

1
Open the POST request

In the collection, open the POST request for LocationName (URL {{baseUrl}}/LocationName).

2
Set a raw JSON body with only data

Body tab → raw → type JSON. Provide just the data object. The script adds the header automatically and ignores any placeholder header the OpenAPI template may include.

{
  "data": { "LocationName": "my-sensor" }
}
3
Send

Make sure the environment is active and enableAuthentication = true, then click Send.

4
Confirm it worked

Open the Console – click Console in the status bar at the bottom-left corner of the Postman window (or use View → Show Postman Console). You should see two calls:

POST http://192.168.0.1/api/getChallenge     200
POST http://192.168.0.1/api/LocationName      200

The device response header should read { "status": 0, "message": "Ok" }.

Prove the auth matters Set enableAuthentication = false and resend. The device returns Access Denied. Set it back to true and it succeeds again.

Concept

7 – What the script does under the hood

For any write request (POST/PUT/PATCH/DELETE) the script fetches a fresh challenge, computes the SHA-256 hash chain, and rewrites the JSON body so the proof travels in the "header" field alongside your "data". GET requests are left untouched.

Postman (pre-request script)                      Sensor
  |                                                |
  |  POST {{baseUrl}}/getChallenge                 |
  |  { "data": { "user": "Service" } }             |
  |──────────────────────────────────────────────►|
  |  { "challenge": { nonce, opaque, realm, salt }}|
  |◄──────────────────────────────────────────────|
  |                                                |
  |  hash1 = SHA256(user:realm:password[:salt])    |
  |  hash2 = SHA256("POST:" + endpointName)        |
  |  response = SHA256(hash1:nonce:hash2)           |
  |                                                |
  |  POST {{baseUrl}}/<endpoint>                    |
  |  { "header": { nonce, opaque, realm,           |
  |                response, user },               |
  |    "data":   { ... } }                         |
  |──────────────────────────────────────────────►|
  |  { "header": { "status": 0, "message": "Ok" }} |
  |◄──────────────────────────────────────────────|
Environment variableRole in the script
baseUrlRoot for both the challenge call and the write.
user / passwordFed into the hash chain. The password never leaves your machine in clear text over the wire.
enableAuthenticationIf not true, the script does nothing.
challengePathWhere getChallenge lives; auto-derived if unset.
Endpoint name in the hash The script derives the endpoint name from the URL path (stripping a leading api/ if present), so both {{baseUrl}}/api/LocationName and {{baseUrl}}/LocationName hash to the same name the device expects.

The Script

8 – Collection-level pre-request script

Copy this entire block into Collection name → Scripts → Pre-request Script. It is self-contained and uses only Postman’s built-in CryptoJS.

/* =============================================================================
 * Challenge-Response Authentication
 * Postman collection-level Pre-request Script
 * (Collection name -> Scripts -> Pre-request Script)
 *
 * DEMONSTRATION USE ONLY
 *   This script is provided for demonstration and evaluation purposes only.
 *   It is not hardened for production use. Review, test, and adapt it to your
 *   own security requirements before using it in a productive environment.
 *
 * Behavior:
 *   For write requests (POST/PUT/PATCH/DELETE) it fetches a challenge from the
 *   device, computes the response hash, and embeds an auth `header` object into
 *   the JSON request body:
 *       { "header": { nonce, opaque, realm, response, user }, "data": { ... } }
 *
 * Environment variables:
 *   baseUrl              scheme + host, e.g. http://192.168.0.1 (no trailing /)
 *   user                 e.g. Service
 *   password             device password (keep as a secret; never commit)
 *   enableAuthentication "true" / "false"
 *   challengePath        (optional) challenge endpoint path override
 *                        (auto-derived from the URL style if unset)
 * ========================================================================== */

(function () {
    'use strict';

    function isTrue(value) {
        if (value === null || value === undefined || value === '') return false;
        try { return JSON.parse(String(value).toLowerCase()) === true; }
        catch (e) { return String(value).toLowerCase() === 'true'; }
    }

    // Convert an array of byte values (0..255) into a CryptoJS WordArray.
    function bytesToWordArray(bytes) {
        var words = [];
        for (var i = 0; i < bytes.length; i++) {
            words[i >>> 2] |= (bytes[i] & 0xff) << (24 - (i % 4) * 8);
        }
        return CryptoJS.lib.WordArray.create(words, bytes.length);
    }

    // Pure algorithm: returns the auth `header` object for the request body.
    function calculateAuthHeader(user, password, challenge, method, path) {
        var ha1;
        if (challenge.salt == null) {
            ha1 = CryptoJS.SHA256(user + ':' + challenge.realm + ':' + password).toString();
        } else {
            var prefix = CryptoJS.enc.Latin1.parse(user + ':' + challenge.realm + ':' + password + ':');
            ha1 = CryptoJS.SHA256(prefix.concat(bytesToWordArray(challenge.salt))).toString();
        }
        var ha2 = CryptoJS.SHA256(method + ':' + path).toString();
        return {
            nonce: challenge.nonce,
            opaque: challenge.opaque,
            realm: challenge.realm,
            response: CryptoJS.SHA256(ha1 + ':' + challenge.nonce + ':' + ha2).toString(),
            user: user
        };
    }

    // --- Guard clauses -------------------------------------------------------
    var method = pm.request.method ? pm.request.method.toUpperCase() : 'GET';
    var fullUrl = pm.variables.replaceIn(pm.request.url.toString());

    if (fullUrl.indexOf('/crown') > -1) return;
    if (['POST', 'PUT', 'PATCH', 'DELETE'].indexOf(method) === -1) return;
    if (fullUrl.indexOf('getChallenge') > -1) return;
    if (!isTrue(pm.environment.get('enableAuthentication'))) return;

    // --- Required variables --------------------------------------------------
    var baseUrl = pm.environment.get('baseUrl');
    var user = pm.environment.get('user');
    var password = pm.environment.get('password');

    if (!baseUrl) throw new Error('[AUTH] Missing environment variable "baseUrl".');
    if (!user) throw new Error('[AUTH] Missing environment variable "user".');
    if (password === null || password === undefined || password === '') {
        throw new Error('[AUTH] Missing environment variable "password".');
    }
    baseUrl = String(baseUrl).replace(/\/+$/, '');

    // --- Derive variable name + challenge URL --------------------------------
    // Variable name = URL path after host, with a leading "api/" removed if present,
    // so both {{baseUrl}}/api/Name and {{baseUrl}}/Name yield the same name.
    var afterScheme = fullUrl.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, '');
    var firstSlash = afterScheme.indexOf('/');
    var pathPart = (firstSlash === -1 ? '' : afterScheme.substring(firstSlash + 1))
        .split('?')[0].split('#')[0].replace(/\/+$/, '');

    var hadApiPrefix = false;
    if (pathPart.indexOf('api/') === 0) { pathPart = pathPart.substring(4); hadApiPrefix = true; }
    var variableName = pathPart.trim();
    if (!variableName) throw new Error('[AUTH] Could not determine variable name from URL: ' + fullUrl);

    var challengePath = pm.environment.get('challengePath');
    if (!challengePath) challengePath = hadApiPrefix ? '/api/getChallenge' : '/getChallenge';
    if (challengePath.charAt(0) !== '/') challengePath = '/' + challengePath;
    var challengeUrl = baseUrl + challengePath;

    // --- Read + resolve the current request body (raw JSON) ------------------
    var rawBody = pm.variables.replaceIn((pm.request.body && pm.request.body.raw) ? pm.request.body.raw : '{}');
    var bodyContent;
    try { bodyContent = JSON.parse(rawBody); }
    catch (e) { throw new Error('[AUTH] Request body is not valid JSON: ' + rawBody); }

    // --- Fetch challenge, compute header, rewrite body -----------------------
    pm.sendRequest({
        url: challengeUrl,
        method: 'POST',
        header: { 'Content-Type': 'application/octet-stream' },
        body: { mode: 'raw', raw: JSON.stringify({ data: { user: user } }) }
    }, function (err, res) {
        if (err) throw new Error('[AUTH] Challenge request failed: ' + err);
        if (!res || res.code !== 200) {
            throw new Error('[AUTH] Challenge request returned HTTP ' + (res ? res.code : 'no response'));
        }

        var challenge;
        try { challenge = res.json().challenge; }
        catch (e) { throw new Error('[AUTH] Challenge response is not valid JSON.'); }
        if (!challenge || !challenge.realm || !challenge.nonce) {
            throw new Error('[AUTH] Unexpected challenge response format: ' + JSON.stringify(challenge));
        }

        var header;
        try { header = calculateAuthHeader(user, password, challenge, method, variableName); }
        catch (e) { throw new Error('[AUTH] Hash calculation failed: ' + e.message); }

        pm.request.body.update({
            mode: 'raw',
            raw: JSON.stringify({ header: header, data: bodyContent.data }),
            options: { raw: { language: 'json' } }
        });
        pm.request.headers.upsert({ key: 'Content-Type', value: 'application/json' });
    });
})();

Troubleshooting

9 – Troubleshooting

SymptomCauseFix
getaddrinfo ENOTFOUND {{baseurl}}No environment selected, so {{baseUrl}} did not resolve.Select the environment in the top-right selector.
"status": 4, "Access Denied"The script did not run (no getChallenge in the Console), or wrong credentials.Confirm the script is in this collection’s Pre-request tab; check user / password.
[AUTH] Missing environment variable "password"Password empty.Enter it in the environment’s Current value column.
No auth on a write, no errorenableAuthentication not true, or method is GET.Set enableAuthentication = true; reads never need auth.
Use the Console as your first stop View → Show Postman Console. A healthy authenticated write shows a getChallenge call (200) immediately followed by the endpoint call (200).
Keywords:
Postman, Challenge-Response authentication, REST API, pre-request script, OpenAPI, SHA-256, collection, environment variables, JSON