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.
- Client requests the same protected resource but this time providing credentials.
- The request is successful and access to the resource is granted.
When communicating over HTTP, this process must be repeated for each request you send to the device. However, for communication over websocket there is no need to pass through steps 1 and 2 more than once: you need to construct the auth
object only once(see more about that below) and then use it for all consecutive requests to that device.
Error 401 and the Authentication Challenge
The error 401 response mentioned in step 2 contains information about:
auth_type
: string, digestnonce
: number, random or pseudo-random number to prevent replay attacksnc
: number, nonce counter (returned only through websocket channel). It has value of 1 if it is not available in the responserealm
: string, device_id of the Shelly devicealgorithm
: string, SHA-256
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}/rpc
ResponseHTTP/1.1 401 Unauthorized
Server: Mongoose/6.18
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="60dc59c6", 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}/rpc
Response{
"id": 1,
"src": "shellypro4pm-f008d1d8b8b8",
"dst": "user_1",
"error": {
"code": 401,
"message": "{\"auth_type\": \"digest\", \"nonce\": 1625038762, \"nc\": 1, \"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
: number, random or pseudo-random number to prevent replay attacks, taken from the error message. Requiredcnonce
: number, client nonce, random number generated by the client. Requiredresponse
: string, encoding of the string<ha1> + ":" + <nonce> + ":" + <nc> + ":" + <cnonce> + ":" + "auth" + ":" + <ha2>
in SHA256. Requiredha1
: string,<user>:<realm>:<password>
encoded in SHA256ha2
: string, "dummy_method:dummy_uri" encoded in SHA256
algorithm
: string, SHA-256. Required
Example:
Request1curl -X POST -d '{"id":1, "method":"Shelly.DetectLocation", "auth":
{"realm": "shellypro4pm-f008d1d8b8b8", "username": "admin", "nonce": 1625038762,
"cnonce": 313273957, "response": "eab75cbbd7acdb7082164cb52148cfbe351f28bf80856f93a23387c6157dbb69",
"algorithm": "SHA-256"}}' \
http://${SHELLY}/rpcOr:
Request2curl --digest -u admin:mypass http://${SHELLY}/rpc/Shelly.DetectLocation
Or:
Request3curl --anyauth -u admin:mypass -X POST -d '{"id":1, "method":"Shelly.DetectLocation"}' http://${SHELLY}/rpc
Response{
"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.
SSL support for outbound connections
Shelly-NG 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.