How to debug/test secure React app?

Is there a way to debug and test a secure React (built from create-react-app) app using the certbot certificates on my system?

I've been using LetsEncrypt with certbot-managed certificates for more than a year with no issues. I understand that they require public access to port 443. This is fine once they're working -- I use the yarn build command from the base directory of the project and deploy the result to a public-facing html directory. All works fine.

Development and testing is a different story. Surely I'm not the first developer to use create-react-app and LetsEncrypt certificates together. I use Visual Studio Code (VSC) and use its facilities for launching a Chrome browser on my local system with special connections back to the dev machine that is running VSC.

Amazingly, the resulting browser runs and correctly communicates with VDE, so that I can set and catch breakpoints and step through React code running in the browser. This is all great. The rub is that I have to click through several challenges to get to the page, and when I get there the browser reports that the page is not secure (annotated screenshot below).

I'm developing and testing site behavior that depends on having a secure connection, and so I don't want to just turn it off. FWIW, I have a related issue (though I think I have work-arounds) for the nodejs middleware services used by the front-end to connect back to the server.

I invite community guidance about how to configure my development environment so that the "start" command issued to react-scripts in the yarn start command line results in a "clean" browser page (see second screenshot below). For any web developers reading this, how do you develop and test secure apps?

Screenshots:


Development browser (dirty)


Clean browser

Requested information

My domain is: byron.zeetix.com
My web server is (include version): Apache/2.4.37 (rocky)
The operating system my web server runs on is (include version): Rocky Linux v8.6
I can login to a root shell on my machine (yes or no, or I don't know): Yes
I'm using a control panel to manage my site (no, or provide the name and version of the control panel): no
The version of my client is (e.g. output of certbot --version or certbot-auto --version if you're using Certbot): certbot 1.30.0

1 Like

You won't be able to get a cert (from any trusted CA) for the name "localhost".
So, that path will always lead to that insecure warning.
You must do one of the following:

  • change to an FQDN that is covered by a trusted cert
    OR
  • produce a self-signed cert that covers that name AND
    explicitly trust that cert within any systems that will be accessing it via encrypted connections
3 Likes

I have a FQDN and am about to generate a trusted cert for it (fullstack.tms.byron.zeetix.com).

The rub is that the VSC uses its own webserver and therefore requires that some port other than 80 or 443 be used for the browser instance instantiated from launch.json.

I can probably stop httpd, so that nothing is listening on 80 or 443. Perhaps that's the best way forward.

My hope is that there is some way to have a successful result from the following URL (note the appended port): https://fullstack.tms.byron.zeetix.com:3000

1 Like

Using the cert on any other port is the easy part.
Getting a cert [without port 80] is not so easy.
The next simplest method is via DNS-01 authentication.
Which, for full automation, requires the use of a DSP that allows zone updates via API calls.

3 Likes

Why not use an already running webserver to get a certificate you could use in React?

By the way, personally I wouldn't access React directly, but always behind a reverse proxy. This reverse proxy can then also do the entire TLS stuff. Especially if you can proxy to localhost, so the connection between webserver and React is secure.

5 Likes

I appreciate the attention of the several who've replied.

I appreciate that this issue may well lie in the intersection between the server and the IDE (VSC). I'm using an app described in "Fullstack React". Once I've published the app (using yarn build), the published app works without complaint at https://fullstack.tms.byron.zeetix.com (screenshot below).

I interpret this to mean that my certificate is valid while being accessed through the usual standard port (443). As noted upthread, I'm using an Apache webserver with the usual virtual host rewrites in /etc/httpd/sites-available and /etc/httpd/sites-enabled. I've included the result of certbot certificates below.

When I append :3000 to the URL, the result says 'Invalid host header` (see screenshot below).

When I use wget (on a different system), I see complaints about the certificate:

$ wget "https://fullstack.tms.byron.zeetix.com:3000"
--2022-09-16 15:39:04--  https://fullstack.tms.byron.zeetix.com:3000/
Resolving fullstack.tms.byron.zeetix.com (fullstack.tms.byron.zeetix.com)... 54.144.238.19
Connecting to fullstack.tms.byron.zeetix.com (fullstack.tms.byron.zeetix.com)|54.144.238.19|:3000... connected.
ERROR: The certificate of ‘fullstack.tms.byron.zeetix.com’ is not trusted.
ERROR: The certificate of ‘fullstack.tms.byron.zeetix.com’ hasn't got a known issuer.
The certificate's owner does not match hostname ‘fullstack.tms.byron.zeetix.com’

When I specify the URL with port in the launch.json, I get the same result.

I use https://localhost:3000 in the launch.json of the IDE because that is the only URL that launches an almost-working browser instance.

Is there way for me to get unstuck here? The above failing wget suggests to me that adding :3000 to the URL breaks the https request before it ever gets to the server.

Hence my question: Is there a way to allow :3000 to be appended to the working URL so that the request is directed to my IDE (which I know is listening on port 3000).

Result of certbot certificates:

# certbot certificates
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Found the following certs:
  Certificate Name: byron.zeetix.com
    Serial Number: 40bf0e1388f3808f32699dc87365e5aeb3f
    Key Type: RSA
    Domains: byron.zeetix.com covid.tms.byron.zeetix.com fullstack.tms.byron.zeetix.com tms.byron.zeetix.com
    Expiry Date: 2022-12-15 18:16:38+00:00 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/byron.zeetix.com/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/byron.zeetix.com/privkey.pem

Successful screenshot:

Failing screenshot:

1 Like

Using the cert on any other port is the easy part.

I have the cert. I eagerly await more information about "the easy part" ... :slight_smile:

While it's easy for you, it is -- so far -- a mystery to me.

By the way, personally I wouldn't access React directly, but always behind a reverse proxy. This reverse proxy can then also do the entire TLS stuff. Especially if you can proxy to localhost, so the connection between webserver and React is secure.

I don't know enough about all this to know what it means to use a reverse proxy. I don't knowingly "proxy to localhost" -- perhaps this is what VLC is doing under the covers.

2 Likes

What service is using port 3000?
How did it get the current cert being used there?

3 Likes

What service is using port 3000?

In the terminal embedded in VSC, things are started by doing yarn start. The project is configured to use the react-scripts module. This starts a development server for the IDE to use and opens a listener on port 3000.

How did it get the current cert being used there?

I don't think it ever succeeds at that. So far, the only way I can get the IDE to launch a development browser is to specify https://localhost:3000 as the URI in launch.json. This produces the screen I've posted earlier, where the development browser is connected to VSC but without the usual green padlock.

1 Like

I haven't studied your whole thread. So, I apologize if this is useless. But, couldn't you just update your local hosts file to resolve your domain to 127.0.0.1 for local testing?

Then use https://(domain):3000 as the URL which would resolve to 127.0.0.1

You already have a cert - right? It's just the name mis-match that's a problem. Is that right?

4 Likes

For integrated testing, we do the following:

Local Developer Tests:

  1. We use DNS-01 challenges to provision a certificate for dev.example.com
  2. We set up public dns to resolve dev.example.com to 127.0.0.1
  3. Developers are encouraged to modify hosts with this entry as well

Local Staging/CI Tests:

  1. We use DNS-01 challenges to provision a certificate for local.staging.example.com
  2. We do not have public DNS records
  3. Each machine configures the hosts file to resolve local.staging.example.com to a LAN IP

Not having any DNS records for the local staging makes it easier to troubleshoot issues.

We have public staging servers too, but those are usually specific release builds.

This lets us ensure all the clients we test with are compatible with the selected LetsEncrypt chain.

Edit: In terms of IDEs and alternate ports like 3000 - your best bet is to run nginx on the local machine and have that forward ports around as needed. You need to make sure that your dev/staging environments mirror the production environments as much as possible.

6 Likes

I haven't studied your whole thread. So, I apologize if this is useless. But, couldn't you just update your local hosts file to resolve your domain to 127.0.0.1 for local testing?

Then use https://(domain):3000 as the URL which would resolve to 127.0.0.1

I made that change, and it has no apparent effect.

I suspect that the IDE (VisualStudio Code) and React magic ('react-scripts') do more than just lookup the domain.

I appreciate your attention!

3 Likes

I have no doubt that this works. Sadly, it appears to me that this requires me (I'm the only developer here!) to climb several new learning curves:

  • DNS-01 challenges: I don't know what that means. I use Route53 for everything related to DNS.
  • The "local" machine is in fact an AWS EC2 instance running linux. I connect to that from my home machine, and I use the domain name (byron.zeetix.com) all over the place.
  • I don't do any local staging/CI tests (at least not yet)
  • I would have to come up to speed with "nginx". I use Apache as the httpd server, so I'd have to learn how to configure and manage nginx on my dev machine (byron.zeetix.com).

I'm left with the distinct impression that this is a topic for the VisualStudio Code and/or React community, rather than this. So far as I can tell, certbot is working just fine.

All this leads me to conclude that what I've got is as good as I can do for now. I'm a bit reluctant to mark it as the "approved" answer because I usually do that only after successfully running whatever it is on my own technology stack. I don't see that happening here in the near future.

1 Like

Yes, you have mixed the concept of react with certificates for TLS.

  • React: a javascript web app, just a bunch of files for your web browser to load.
  • TLS: something you configure on your web server, in order to serve your app files via https.

The two things are conceptually related but in practice are two completely different topics - like learning to drive so you can go to a McDonalds drive thru.

Yes you can automate all of this but in reality all you really want to do is to configure your web app in Apache with a cert (using certbot), which is just standard certbot (or any other acme client).

When you subsequently update the app you're just updating your web app files, not touching the certificate (because your web app and your web server config are two very different and independent things).

5 Likes

(emphasis mine)

When you subsequently update the app you're just updating your web app files, not touching the certificate (because your web app and your web server config are two very different and independent things).

I appreciate your attention, mostly agree with you, and hearted your reply.

In a more perfect world, they might be independent. In the real world, they are not. The code pushed to a browser after deployment is at best similar to the code run and tested in the IDE -- that's why yarn start is different from yarn build. The code paths that involve certs will be handling different data in the two worlds if the certs are different. Some of us who remember building compilers with yacc and lex have scar tissue from bugs in those tools that caused obscure and very difficult to fix problems that were provoked but not caused by our own code.

I'm declaring what I have as "good enough" because I'm reasonably confident that the behavior that produces the ugly result in the very top screenshot on this topic happens inside the (Chrome) browser and is done once the browser is connected back to the IDE (through port 3000). So far as I've been able to discern (stepping through code execution in the IDE and looking at variable values), the application code itself that is under my direct control is being exercised. It appears to me from this thread that is about the best I can do with today's tools (I'm a one-person shop right now) -- and that's good enough.

So while I wish I could see a clean browser when I launch the browser from the IDE, it seems clear that too much heavy lifting stands between where I am and that outcome -- heavy lifting that I think has little more than cosmetic value.

I do wish that our professional development community would be a bit more disciplined about how we use words like "independent", "secure", and so on. I'm of the opinion that ANY code pushed to a browser that can't be run in a test environment is untested code. Since most shops don't have time or inclination to even aspire to 100% test coverage (I certainly do not!), code that can't be examined in a debugger and can't be tested is unexamined and untested code. I'm in the habit of minimizing that as best I can.

The reason the hairs on my arm tingle when we use vocabulary like "independent" is that at least in this case, I think we use the word to mean "untestable and undebuggable". When I wear my math hat, I think that "independent" means that I can change one without affecting the other. That isn't the case here. The web app, web server config, and a host of other parts are connected in a serial pipeline -- bugs and errors in one have dramatic impact on downstream participants.

I can tell you that in my several decades of professional software development, the failures that prove most expensive (in money, market reputation, or career advancement) are glaringly obvious bugs in code fragments that are very important, very hard to test, and that cover scenarios that are very rare. A typical example is an exception handler for a rare and catastrophic failure situation. You really want to clean things up when you get the exception that says that the triple-redundant high-availability power supply has just failed. It's generally a bad thing when that cleanup code has a syntax error that isn't detected because it's dynamically loaded and never even compiled until the failure happens. Nobody can figure out how to inject that failure into the test harness and therefore it never got compiled, never mind tested. I can tell you that in my time at big companies like IBM and Experian, failures like these were not unheard of.

In this case, I'm confident that the certbot infrastructure is reasonably solid. I'm equally confident the browser behavior is robust enough that I'm unlikely to find and fix issues within it. My bias is that the more complexity I add to the technology landscape -- extra web servers, more DNS services, more certs, more ways to check certs, etc -- the more likely I am to introduce failures that I can only discover when I get a ticket from my user.

So I think I'll stick with what I've got.

1 Like

I'm not 100% sure this will fix the issue as I am not familiar with Visual Studio Code or how it runs this web server but you might try going to chrome://flags and enabling the "allow-insecure-localhost" value and restarting the web browser. This should stop Chrome from giving your warnings on localhost. I can't think of any real security problems this might open you up to off the top of my head as long as you aren't doing other weird things on localhost but I would still recommend disabling the flag when you are done using it.

EDIT:
After reading the VS code docs on browser debugging it looks like it uses a separate profile for either Edge or Chrome (whichever it launched) so make sure you change the flag from the "debug" browser window it opens so that it's applied to that profile, not your personal one. (Also if it's Edge the URL will be edge://flags of course.)

2 Likes

Indeed, I'm pretty sure the built-in VSC chrome debug configuration (in launch.json) does that for the browser instance it spawns. I only occasionally get the challenges.

The red "Not secure" and crossed-out "https" happen all the time, though. I don't think there's a way to disable that short of somehow making the certificate negotiation succeed.

Thanks for taking the effort to investigate and reply!

3 Likes

Darn, worth a shot. It's unusual that you only "occasionally" get challenges though, I can't think of why that would happen. Either Chrome/Edge was launched with the flag or not. If you are interested enough to poke around you might try checking with a task manager what command line arguments were passed to the "debug" browser to see why this might be happening and if it really is sometimes getting passed different args. Or it might not be worth it if it isn't that much of a bother to you. Sorry that the suggestion didn't help.

2 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.