Azure was chosen as cloud service provider in this project. Any mention of "cloud" means that it's using some Azure specific infrastructure.
Main components are shown below.
This document explains every element above and choices made when designing this infrastructure.
Serverless
Section titled “Serverless”It's important to mention before going any further that every element of this system is fully serverless, or to be more precise, scales to 0. Only constant cost is that of background worker which runs every 24 hours.
The gateway tf docs
Section titled “The gateway tf docs ”Starting with main entry point of cloud infrastructure, the gateway. It's Azure's API Management consumption tier. Every request (from devices and the application) enters through it.
The gateway does two things before any backend sees a request. It validates the JWT token attached to the request, and it routes the request to the correct service based on the URL path.
Authorization is handled at the gateway level. It doesn't trust the device's token directly for backend calls. Any JWT is first validated, then its device ID is extracted from the claims. The backend never sees the device's original credential.
Authorization is described in detail here.
Services tf docs
Section titled “Services tf docs ”Behind the gateway sit two Azure Function Apps, each on its own service plan (Flex Consumption FC1). They handle different concerns and never call each other.
Provisioning service tf manages device identity. It handles the provisioning flow and user claiming (more in Authorization). It runs a custom runtime (Go) and has access to Key Vault for the token-signing private key.
Worker service tf handles weather data. It is the ingress for all telemetry and runs the daily finalization flow. It runs on .NET 8 isolated runtime.
Both services authenticate to their backends through managed identity. The gateway obtains a token from Azure AD on behalf of the function app it's calling (the function never exposes a shared key or connection string to the outside).
Both services write to the same CosmosDB account but touch different containers. They share no state except the database itself.
The database tf docs
Section titled “The database tf docs ”Database of choice for this project is CosmosDB. It runs in serverless mode (pay-per-request, no provisioned throughput).
The account uses session consistency (the default) and a single region (West Europe) with no geo-replication.
There are three containers, all partitioned by /deviceId:
Device registry stores one record per device: its HMAC secret, claim status, and owner. The provisioning service is the only writer.
Views holds the materialized aggregates that dashboards read (daily buckets, weekly buckets). The worker writes here on every telemetry upload.
Telemetry raw is the audit log. An exact copy of every payload, auto-deleted after 30 days via TTL.
Key Vault tf docs
Section titled “Key Vault tf docs ”Key Vault stores the RSA private key used by the provisioning service to sign device session tokens. The provisioning function reads it at startup through a Key Vault reference in its app settings, no secret ever appears in source code or deployment config.
Static Web App (JWKS host) tf docs
Section titled “Static Web App (JWKS host) tf docs ”The gateway needs the public key to verify a JWT's signature. This project uses Static Web App to simulate OpenID configuration endpoint which gateway uses.
Azure Static Web App has a generous free tier that covers this use case.
It serves two OpenID configuration documents, one for device tokens (/device/.well-known/openid-configuration) and one for provisioning tokens (/provisioning/.well-known/openid-configuration). The gateway's validate-jwt policy points at these URLs. When a request arrives, the gateway fetches the public key from the static app and validates the signature locally.
Storage Account tf docs
Section titled “Storage Account tf docs ”A single Storage Account (Standard LRS) holds deployment packages for both function apps in separate blob containers. It also serves as the backing store for Azure Functions' internal state (AzureWebJobsStorage).
Identity tf docs
Section titled “Identity tf docs ”The system uses Azure AD for all inter-service authentication — no shared secrets, no connection strings passed between services.
The flow differs by endpoint type. For device endpoints, a device sends its JWT to the gateway, where it's validated and then the request gets forwarded to the appropriate service.
Administrative endpoints are different, the application must independently prove it's identity to Azure AD. The application retrieves its secret from config, exchanges it for an access token, and attaches that token to the request. The gateway then validates it in the same way it does for device requests.
Observability tf docs
Section titled “Observability tf docs ”Application Insights collects traces from both function apps. Both functions include the Application Insights agent extension, which provides distributed tracing, dependency tracking, and failure alerts.
What this costs
Section titled “What this costs”At low volume (a handful of devices), the entire cloud layer runs within Azure's free tiers. API Management charges per call, Functions charge per execution, and CosmosDB Serverless charges per request unit consumed. Most of the costs come from reading storage account on cold starts.
The trade-off is cold start time. Serverless functions that haven't run recently take a few seconds to warm up. A device waking from deep sleep won't notice, but a user opening up a dashboard might.