Skip to content

SolarNet API authentication scheme Message Signatures

Matt Magoffin edited this page Sep 19, 2026 · 3 revisions

SolarNetwork HTTP Message Signatures (RFC 9421)

SolarNetwork accepts HTTP Message Signatures (RFC 9421) alongside the SNWS2 authentication scheme. Both use the same security token credentials: a token ID and a token secret. Security tokens work for both RFC 9421 and SNWS2 authentication simultaneously: you can freely switch between the two using the same token credentials.

This document specifies the profile of RFC 9421 required by SolarNetwork.

API Explorer

See https://go.solarnetwork.net/dev/api/ for a JavaScript client implementation of this authentication scheme designed to showcase how the API can be used.

Terms

The following table describes some terms used throughout this document.

Term Description
tokenId The SolarUser generated token value. This is the value shown in the SolarUser token management screen.
tokenSecret The SolarUser generated token secret. This value is shown only once in the SolarUser token management screen, when the token is first created.
HMAC() A HMAC+SHA256 encoding function that accepts a secret key and message data as arguments, e.g. HMAC(key, message) and produces a 32-byte HMAC signed SHA256 digest

Presenting a signature

RFC 9421 uses two HTTP headers to provide the signature SolarNetwork uses for authentication: Signature-Input (defining what has been signed) and Signature (the actual signature). SolarNetwork takes the Signature-Input headers and recomputes the signature based on what was actually provided in the HTTP request. If the computed signature matches the one given in the Signature header the request will be successfully authenticated as the token ID provided and allowed to proceed.

Here is an example of what these headers look like:

Signature-Input: sn=("@method" "@authority" "@path" "@query");created=1789761471\
;keyid="MY-TOKEN-ID";tag="solarnetwork"
Signature: sn=:yt00kJV0rsRBmP7Pu7C8J2f+avzPAAzv1vEGX+SrCd8=:

SolarNetwork detects an RFC 9421 request by the presence of Signature-Input, as RFC 9421 appendix A recommends. If a request carries an Authorization header naming a supported scheme, that takes precedence.

The signature label (sn above) can be anything.

Signature input parameters

The following table outlines the signature parameters supported by SolarNetwork:

Parameter Required Rule
keyid yes The token ID, optionally with a signing key date; see Signing keys
created yes Must be within the server's allowed date skew, which is 15 minutes
expires no If given, must not be in the past
alg no If given, must be hmac-sha256
nonce no Accepted and ignored
tag no See Choosing a signature

Covered message components

RFC 9421 lets the signer decide what message components of the HTTP request to cover (include) in its signature. SolarNetwork requires the following minimum set of items:

  • @method
  • @authority and @path together, or @target-uri
  • @query — required when the request has a query string
  • content-digest — required when the request has content; see Request content
  • content-type — required when the request includes this header
  • every X-SN-* header the request includes

Any additional component may be covered. The sf, key, bs and name component parameters are supported.

These are not supported and will be rejected:

  • the tr parameter — trailer fields are not supported
  • the req parameter — not valid on a request signature (RFC 9421 §2.4)
  • @status — applies to responses
  • the same component identifier covered more than once

Signing keys

The keyid parameter provides both the token ID and how its secret becomes the signing key. Both forms are accepted; the derived key is the one SolarNetwork's own tooling uses by default, and the one to prefer.

keyid="<token ID>:<YYYYMMDD>" — derived key, recommended

The signing key is derived from the token secret and a UTC date:

keyid="MY-TOKEN-ID:20260918"
  →   K = HMAC(HMAC("SNWS3" + tokenSecret, "20260918"), "snws3_request")

This works the way an SNWS2 signing key does, and has the same benefit: the derived key is only valid for 7 days from its date, so it can be given to whatever signs the request without disclosing the token secret itself. The date must not be more than 6 days before, or 1 day after, the UTC date of the created parameter.

keyid="<token ID>" — token secret

The signing key is the UTF-8 encoded token secret:

keyid="MY-TOKEN-ID"   →   K = UTF8(tokenSecret)

Any RFC 9421 implementation can sign this way, with nothing but the token ID and secret and no knowledge of SolarNetwork's key derivation. That makes it the simplest way to get started, at the cost of handing the secret to the signer.

Request content

If the HTTP message includes body content, an RFC 9530 Content-Digest header must be included, and treated as a covered message component. SolarNetwork supports the sha-256 and sha-512 digest schemes, for example:

Content-Digest: sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:

Note this is a different header than the RFC 3230 Digest field the SNWS2 scheme accepts. A Content-Digest on the request is validated against the actual content whether or not it is covered by the signature, and a request with content must cover it.

This includes application/x-www-form-urlencoded content. The SNWS2 scheme signs form parameters as if they were query parameters; RFC 9421 does not so form content needs a Content-Digest like any other content.

Choosing from multiple signatures

A request may carry more than one signature, for example when a proxy adds its own. To say which one SolarNetwork should authenticate with, give it a tag parameter with the value solarnetwork:

;tag="solarnetwork"

If no signature carries the tag and exactly one signature is present, that one is used. If no signature carries the tag and several are present, the request will be rejected.

Rejected requests

A rejected request gets a 401 with the reason in the X-SN-ErrorMessage header, and an Accept-Signature field describing what an acceptable signature must cover:

Accept-Signature: sn=("@method" "@authority" "@path" "@query");created;keyid\
;tag="solarnetwork"

Worked example

For this request:

GET /solarquery/api/v1/sec/datum/stream/datum?nodeId=123&sourceId=meter HTTP/1.1
Host: localhost:9082

signed with token 12345678901234567890, secret lsdjfpse9jfoeijfe09j, at created=1789761471 (2026-09-18 UTC), the signature base is:

"@method": GET
"@authority": localhost:9082
"@path": /solarquery/api/v1/sec/datum/stream/datum
"@query": ?nodeId=123&sourceId=meter
"@signature-params": ("@method" "@authority" "@path" "@query");created=1789761471\
;keyid="12345678901234567890:20260918";tag="solarnetwork"

and HMAC-SHA256 of that, keyed with the derived signing key HMAC(HMAC("SNWS3lsdjfpse9jfoeijfe09j20260918"), "snws3_request"), gives:

Signature-Input: sn=("@method" "@authority" "@path" "@query");created=1789761471\
;keyid="12345678901234567890:20260918";tag="solarnetwork"
Signature: sn=:8gcZ/Rdd932qOF1Cm+hpu7M/hQQ92A3dCffFxtyTSWg=:

Signing the same request with the token secret directly — keyid of just 12345678901234567890 — gives Signature: sn=:yt00kJV0rsRBmP7Pu7C8J2f+avzPAAzv1vEGX+SrCd8=: instead.

The API Explorer shows these steps for any request you give it, and generates a matching curl command.

Clone this wiki locally