Authentication
Communication through HTTP and Websocket channels is secured by a digest authentication mechanism using the SHA256 hmac algorithm as defined in RFC7616.
When enabled, all communication is protected except:
- Communication over
MQTT - Communication over
UART - Communication over
Outbound Websocket - the RPC method
Shelly.GetDeviceInfo - the HTTP endpoint
/shelly
Setting Authentication Credentials for a Device
Authentication can be enabled by setting authentication details through the RPC method Shelly.SetAuth. A pre-calculated ha1 parameter is expected - this is the result of SHA256(<user>:<realm>:<password>). Please refer to RFC7616 for further details on the mechanism.
Example:
HA1 calculation with JavaScriptlet crypto = require('crypto');
let username = 'admin'; // always
let password = 'mysecretpassword';
let realm = 'shellymodel-XXXXXXXXXXXX';
let auth_parts = [username, realm, password];
let ha1 = crypto.createHash("sha256").update(auth_parts.join(':')).digest("hex");
console.log(ha1);
Authentication process
Steps in the process:
- Client requests a protected resource without providing credentials.
- Server response containing error 401 (unauthorized) is received with a nonce.
- Client requests the same protected resource but this time providing credentials including the nonce.
- The request is successful and access to the resource is granted.
Efficient Authentication with Nonce Reuse (Firmware 2.0.0+)
Starting with firmware 2.0.0, clients can reuse the same nonce for multiple requests by incrementing the nc (nonce count) parameter:
This allows clients to avoid the 401 challenge-response overhead for every request, significantly improving communication efficiency.
Nonce Handling (Firmware 2.0.0+)
Starting with firmware 2.0.0, the device implements RFC 7616-compliant nonce management:
- Nonce validity: Each nonce is valid for 1 hour from the time it was issued.
- Nonce reuse: A single nonce can be reused for up to 30,000 requests by incrementing the
nc(nonce count) parameter with each request. - Replay protection: The device tracks the nonce count and rejects requests with an
ncvalue that is not strictly greater than the previously seen value for that nonce. - Stale nonces: When a nonce expires or exceeds its usage limit, the device returns a
401response withstale=true, indicating the client should obtain a new nonce without re-prompting the user for credentials.
This allows for efficient communication over both HTTP and WebSocket:
- HTTP: You can reuse the same nonce across multiple requests by incrementing
nceach time, avoiding the overhead of obtaining a new nonce for every request. - WebSocket: Construct the
authobject once and reuse it for consecutive requests, incrementingncfor each call.
Nonce Tracking and Eviction (Firmware 2.0.0+)
The device maintains a circular buffer of up to 32 nonce entries to track active sessions:
| Nonce State | Description |
|---|---|
| Pending | Nonce issued in a 401 challenge, client hasn't responded yet (nc not yet received) |
| Active | Client has authenticated at least once with this nonce |
Eviction policy when the buffer is full:
- Empty slots are used first.
- Least-used active sessions (sessions used only once) are evicted next.
- Pending challenges are protected - unanswered 401 challenges are not evicted in the first pass.
- If no suitable slot is found, a 2-second throttle window begins. During this time, requests for new nonces return HTTP 429 (Too Many Requests).
- After the throttle window expires, the slot with the minimum usage count is force-evicted (including pending challenges if necessary).
This design ensures that:
- Active sessions with ongoing communication are preserved longer.
- Pending authentication challenges have time to complete.
- The device gracefully handles high connection loads with temporary throttling rather than immediate eviction.
Brute-Force Protection (Firmware 2.0.0+)
The device implements brute-force protection to prevent password guessing attacks:
- Failed authentication attempts are tracked using a 10-minute sliding window.
- After multiple failed attempts, the device enforces progressive backoff delays:
- 1-10 failures: No delay
- 11-20 failures: 10 seconds between attempts
- 21-30 failures: 30 seconds between attempts
- 31-40 failures: 60 seconds between attempts
- 40+ failures: 5 minutes between attempts
- When throttled, the device returns HTTP status 429 (Too Many Requests) instead of 401.
- Successful authentication with a fresh nonce resets the failure counters.
- Brute-force counters are reset on the device reboot/reset.
Error 401 and the Authentication Challenge
The error 401 response mentioned in step 2 contains information about:
auth_type: string, digestnonce: string, base64-encoded cryptographic token (firmware 2.0.0+) or numeric value (prior versions)nc: number, nonce counter (returned only through websocket channel). (removed in firmware 2.0.0+)realm: string, device_id of the Shelly devicealgorithm: string, SHA-256stale: boolean (optional), whentrueindicates the nonce has expired but credentials are valid - client should retry with a new nonce (since firmware 2.0.0)
Authentication over HTTP
When communicating through a HTTP channel this information can be found in the WWW-Authenticate header of the response.
Example:
Requestcurl -X POST -i -d '{"id":1, "method":"Shelly.GetStatus"}' http://${SHELLY}/rpcResponseHTTP/1.1 401 Unauthorized
Server: ShellyHTTP/1.0.0
Content-Type: application/json
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Content-Length: 0
Connection: close
WWW-Authenticate: Digest qop="auth", realm="shellypro4pm-f008d1d8b8b8", nonce="AAAAAABn...(base64)...", algorithm=SHA-256
Authentication over Websocket
When communicating through a websocket channel, the response contains error frame where the above attributes can be found.
Example:
Requestcurl -X POST -d '{"id":1, "method":"Shelly.DetectLocation"}' http://${SHELLY}/rpcResponse{
"id": 1,
"src": "shellypro4pm-f008d1d8b8b8",
"dst": "user_1",
"error": {
"code": 401,
"message": "{\"auth_type\": \"digest\", \"nonce\": \"AAAAAABn...(base64)...\", \"realm\": \"shellypro4pm-f008d1d8b8b8\", \"algorithm\": \"SHA-256\"}"
}
}
Successful Request with Authentication Details
In order to make the request work properly, an additional auth JSON object should be added to the request frame. It should contain:
realm: string, device_id of the Shelly device. Requiredusername: string, must be set to admin. Requirednonce: string, the nonce value from the authentication challenge. Requiredcnonce: number, client nonce, random number generated by the client. Requirednc: string, nonce count - a client-side monotonically increasing counter represented as 8 hexadecimal digits padded with leading zeros (e.g., "00000001", "0000000a"). Usually starts from "00000001" for a fresh nonce and increment with each request. Requiredresponse: string, the digest response calculated as described below. Requiredalgorithm: string, SHA-256. Required
Calculating the Response
The response field is calculated as: SHA256(<ha1>:<nonce>:<nc>:<cnonce>:auth:<ha2>)
Where:
ha1:SHA256(<user>:<realm>:<password>)- can be pre-computed and storedha2:SHA256(<method>:<uri>)- depends on the transport type:
| Transport | Method | URI |
|---|---|---|
| HTTP | The actual HTTP method (e.g., "GET", "POST") | The actual request URI (e.g., "/rpc/Shelly.GetStatus") |
| WebSocket RPC | "dummy_method" | "dummy_uri" |
| Other RPC transports | "dummy_method" | "dummy_uri" |
Example for WebSocket/RPC:
Response calculation for WebSocketlet ha1 = "..."; // pre-calculated SHA256(admin:realm:password)
let ha2 = crypto.createHash("sha256").update("dummy_method:dummy_uri").digest("hex");
let response = crypto.createHash("sha256")
.update(`${ha1}:${nonce}:${nc}:${cnonce}:auth:${ha2}`)
.digest("hex");
Example for HTTP:
Response calculation for HTTPlet ha1 = "..."; // pre-calculated SHA256(admin:realm:password)
let method = "POST";
let uri = "/rpc";
let ha2 = crypto.createHash("sha256").update(`${method}:${uri}`).digest("hex");
let response = crypto.createHash("sha256")
.update(`${ha1}:${nonce}:${nc}:${cnonce}:auth:${ha2}`)
.digest("hex");
Example request:
Request1curl -X POST -d '{"id":1, "method":"Shelly.DetectLocation", "auth":
{"realm": "shellypro4pm-f008d1d8b8b8", "username": "admin", "nonce": "AAAAAABn...(base64)...",
"cnonce": 313273957, "nc": "00000001", "response": "eab75cbbd7acdb7082164cb52148cfbe351f28bf80856f93a23387c6157dbb69",
"algorithm": "SHA-256"}}' \
http://${SHELLY}/rpcOr:
Request2curl --anyauth -u admin:mypass http://${SHELLY}/rpc/Shelly.DetectLocationOr:
Request3curl --anyauth -u admin:mypass -X POST -d '{"id":1, "method":"Shelly.DetectLocation"}' http://${SHELLY}/rpcResponse{
"id": 1,
"src": "shellypro4pm-f008d1d8b8b8",
"dst": "user_1",
"result": {
"tz": "Europe/Sofia",
"lat": 42.67236,
"lon": 23.38738
}
}
Client implementations
NodeJS sample is provided on GitHub.
HomeAssistant's Shelly integration is an excellent example of a Websocket Shelly client with authentication support in Python.
Prior to Firmware 2.0.0
The following section describes the authentication behavior in firmware versions prior to 2.0.0. If you are developing new integrations, please refer to the sections above for the current implementation.
Nonce Handling (Legacy)
In firmware versions prior to 2.0.0:
- Nonces were simple numeric values (e.g.,
1625053638) - There was no server-side tracking of nonce counts
- Nonces did not have a built-in expiration mechanism
- Each HTTP request required going through the full 401 challenge-response cycle
Authentication Details (Legacy)
The auth object parameters:
realm: string, device_id of the Shelly device. Requiredusername: string, must be set to admin. Requirednonce: number, the numeric nonce from the error message. Requiredcnonce: number, client nonce, random number generated by the client. Requirednc: string, nonce count - a client-side counter, usually has value of "00000001".response: string, calculated usingha1,ha2,nonce,cnonceandnc. Requiredha1: string,<user>:<realm>:<password>encoded in SHA256ha2: string, "dummy_method:dummy_uri" encoded in SHA256
algorithm: string, SHA-256. Required
Note: In the legacy implementation, nc was not tracked server-side, so clients typically used "00000001" for all requests.
Authentication Challenge Example (Legacy)
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digest qop="auth", realm="shellypro4pm-f008d1d8b8b8", nonce="60dc59c6", algorithm=SHA-256
{
"id": 1,
"src": "shellypro4pm-f008d1d8b8b8",
"dst": "user_1",
"error": {
"code": 401,
"message": "{\"auth_type\": \"digest\", \"nonce\": 1625053638, \"nc\": 1, \"realm\": \"shellypro4pm-f008d1d8b8b8\", \"algorithm\": \"SHA-256\"}"
}
}
Legacy Request Example
curl -X POST -d '{"id":1, "method":"Shelly.DetectLocation", "auth":
{"realm": "shellypro4pm-f008d1d8b8b8", "username": "admin", "nonce": 1625053638,
"cnonce": 313273957, "response": "eab75cbbd7acdb7082164cb52148cfbe351f28bf80856f93a23387c6157dbb69",
"algorithm": "SHA-256"}}' \
http://${SHELLY}/rpc
Note: In the legacy implementation, the nonce was a number rather than a base64-encoded string, and there was no brute-force protection mechanism.
SSL support for outbound connections
Gen2+ provides secure socket support for:
- MQTT with support for CA bundle selection
- Webhooks (only built in CA bundle supported, support for CA selection will be added in 0.8.0)
- The RPC method
HTTP.GET
Internally, TLS is provided by the industry-standard mbedtls library. Certificate validation is supported, and users can choose between no validation, a built-in CA bundle and a user-provided CA bundle.