One common issue when creating web apps for the Philips Hue ecosystem is that Hue Bridges ONLY support https, and the SSL certificate installed on the bridge is INVALID.

What happens when you try to access the Hue Bridge by its IP address? You will get a warning:

Your connection is not private

You can click on Advanced and then click a link to proceed to the ip address.
The browser's address bar will show a warning that its not secure.

So we can work around this when accessing the bridge directly. But what if you create a website that does a JavaScript request to the Hue Bridge? It will just fail. The developer console in the browser will show an error, but for the user, it just fails.
There is no option to tell your browser to trust Javascript calls to this invalid certificate or to just ignore it.

Ignoring SSL on the backend

When creating an iOS or Android app, you can tell your app to ignore SSL errors. When I'm developing my HueApi library for C#, the library also ignores the invalid SSL certificate and lets the requests go through.

During the development of Hue Entertainment Pro, I also had the need to do web requests to the Hue Bridge. It's a Blazor Web App, it runs C#, which gets compiled to WebAssembly.
And even though its the same code running on the server and on the frontend, telling it to ignore the SSL warnings still does not work. This is prohibited by the browser which it is running in.

Reverse Proxy

Hue Entertainment Pro runs on a server, so I can use the server to proxy all requests. I created a generic Hue Bridge proxy using YARP.
Every request to http://my-ip/hue-proxy/HUE_BRIDGE_IP/ will be proxied to the HUE_BRIDGE_IP
So for example, a request to http://my-ip/hue-proxy/192.168.0.42/resources/ will be proxied by the server to https://192.168.0.42/resources/

Advantages:

  • My server can run on http, it doesn't need SSL.
  • It will do requests to the Hue Bridge over SSL and will ignore SSL errors from the bridge.
  • This way I'm able to make requests to the hue-proxy URL as if I'm requesting to a real bridge. It's fully compatible with the Hue API because every request is sent to the real Hue API. This lets me also use the HueApi library, instead of giving it the real IP address of the Hue Bridge, I'm giving it the hue-proxy URL as base address.
  • It's fully transparent for the client code.

YARP

YARP is a great Microsoft library to create reverse proxies like this. Below is the code I've used to create this reverse proxy to my Hue Bridge.

public static IServiceCollection AddHueProxyService(this IServiceCollection services)
    {

      services.AddReverseProxy()
      .LoadFromMemory(
      [
        new RouteConfig()
        {
          RouteId = "hue-proxy-route",
          ClusterId = "hue-proxy-cluster",
          Match = new RouteMatch
          {
              Path = "/hue-proxy/{ip}/{**catchall}"
          }
        }
      ],
      [
        new ClusterConfig()
        {
          ClusterId = "hue-proxy-cluster",
          Destinations = new Dictionary<string, DestinationConfig>(StringComparer.OrdinalIgnoreCase)
          {
              { "destination1", new DestinationConfig() { Address = "http://placeholder/" } }
          },
          HttpRequest = new ForwarderRequestConfig()
          {
              ActivityTimeout = TimeSpan.FromSeconds(30)
          },
          HttpClient = new HttpClientConfig
          {
              DangerousAcceptAnyServerCertificate = true
          }
        }
    ])
    .AddTransforms(builderContext =>
    {
      // Extract IP, validate, and transform the destination URL in one transform
      builderContext.AddRequestTransform(transformContext =>
      {
        var path = transformContext.HttpContext.Request.Path.Value;

        if (!string.IsNullOrEmpty(path) && path.StartsWith("/hue-proxy/"))
        {
          var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries);

          if (segments.Length >= 2)
          {
            var ipAddress = segments[1];

            // Validate IP address format
            if (IPAddress.TryParse(ipAddress, out _))
            {
              if (segments.Length >= 3)
              {
                // Remove "hue-proxy" and IP segments, keep the rest
                var newPath = "/" + string.Join("/", segments.Skip(2));
                var queryString = transformContext.HttpContext.Request.QueryString.Value;
                var newUrl = $"https://{ipAddress}{newPath}{queryString}";
                transformContext.ProxyRequest.RequestUri = new Uri(newUrl);

                // Update Host header to match target
                transformContext.ProxyRequest.Headers.Host = ipAddress;
              }
              else
              {
                // Handle case where there's only IP but no additional path
                var newUrl = $"https://{ipAddress}/";
                transformContext.ProxyRequest.RequestUri = new Uri(newUrl);
                transformContext.ProxyRequest.Headers.Host = ipAddress;
              }
            }
            else
            {
              // Invalid IP address - return 400 Bad Request
              transformContext.HttpContext.Response.StatusCode = 400;
              transformContext.HttpContext.Response.WriteAsync($"Invalid IP address format: {ipAddress}");
              return ValueTask.CompletedTask;
            }
          }
          else
          {
            // Missing IP address in route
            transformContext.HttpContext.Response.StatusCode = 400;
            transformContext.HttpContext.Response.WriteAsync("Missing IP address in route");
            return ValueTask.CompletedTask;
          }
        }

        return ValueTask.CompletedTask;
      });

      // Optional header customization
      builderContext.AddRequestHeaderRemove("X-Forwarded-Host");
      builderContext.AddRequestHeader("X-Proxied-By", "HueProxy");
    });

      return services;
    }