React UI that authenticates with Okta and talks over a websocket to a Java backend

We need to serve a React UI that authenticates with Okta and talks over a websocket to a Java backend. Java and React are each running in a Docker container. We want all of this running on a single machine behind Caddy under the same subdomain, and each docker container is on a separate port.
I know we are supposed to suggest previous attempts at succeeding, but after a previous Caddy post it has led us to thinking that we need to question our whole approach overall to tackling this issue, and thus would like to see any suggestions the community has.
We are running Caddy using version 2.0, via Docker-compose, and we are feeding it a Caddyfile.

I wouldn’t even know where to start with answering this, there’s not really enough information about how you expect this to work. Are these supposed to each be under different subdomains? Do you have any specific paths that need to be handled specifically? I don’t really understand.

All under same subdomain. If they are applications on different ports, I would assume they have to be different paths. Meaning that if you just have example.com/ for both applications, it would not know which port to forward to?

Is Okta a HTTP endpoint?

Websockets are fine - in fact, we can test for one and proxy those on the same path, actually. Depending on how Okta works - I’m not familiar with it - but if it sends some specific header or makes a specific kind of request we can grab that and siphon it off to Okta, too.

subdomain.example.com {
  # Java backend
  @websocket {
    header Connection *Upgrade*
    header Upgrade websocket
  }
  reverse_proxy @websocket java_backend

  # Okta authentication
  @auth header Some-Okta-Header *
  reverse_proxy @auth okta_backend

  # Serve site
  root * /var/www/html
  file_server
}

Dunno if your React UI needs to try_files or if you’ll need some other way of picking out authentication requests (by subpath maybe). All super dependent on you.

1 Like

I guess it be best to start one step at a time… The proper approach seems to serve a react app not through a proxy, but through a file server. I have the following, and it works:

domain

handle / {
    root * /app/build
    file_server
}

log {
  level DEBUG
}

This works fine, and it serves it properly to domain/.
I want to get this though on domain/ui, and I can’t figure that out.
I have tried:

domain

redir /ui /ui/
handle /ui {
    root * /app/build
    file_server
}

log {
  level DEBUG
}

I have also tried:

domain

redir /ui /ui/
handle /ui {
    root /ui /app/build
    file_server
}

log {
  level DEBUG
}

I have also tried:

domain

redir /ui /ui/
handle /ui {
    root /ui/* /app/build
    file_server
}

log {
  level DEBUG
}

I didn’t post any other info, bc if it works with /.

In Caddy v2, path matching is exact-match. That means that / will only match requests to /, and /ui will only match requests to /ui. You need to append a * to tell Caddy to match any path with that prefix.

E.g.

redir /ui /ui/
handle /ui/* {
    root * /app/build
    file_server
}

So my caddyfile should become:

domain

redir /ui /ui/
handle /ui/* {
    root * /app/build
    file_server
}

log {
  level DEBUG
}

?

Yeah that’s a valid Caddyfile, but I don’t know if it’ll work for your usecase :smile:

:sweat_smile: sorry, i know ive been at this for days

This does not work. caddyfile is:

domain

redir /ui /ui/
handle /ui/* {
    root * /app/build
    file_server
}

log {
  level DEBUG
}

If i take out the handle at /ui/*, then it serves a react application. The build is loaded into /app/build in a docker container, and that is I’m assuming the function of root * app/build, and the file_server will read in there. Something about changing the route on the subdomain/ to subdomain/ui, makes it unable to find the build folder

sorry more accurate to say: I believe the function of root* app/build, is to tell the file server to look in the docker container to find the build to serve

Okay, try this:

domain

root * /app/build

try_files {path} /index.html

file_server

log

I will give this a shot, but are you suggesting to put that inside the handle /ui/*? or just restart the caddyfile. if so, how does it get to the ui route?

I don’t think the handle is necessary here if the React build is right in /app/build. Is the app using a frontend router? If that’s the case, then what you typically want is for any path that doesn’t map to a file on disk to actually serve index.html instead such that it always loads the react app for any path that isn’t a static asset like your app.js or app.css, etc.

But I’m just making assumptions here, I have no idea what the application looks like. What’s actually in /app/build? (Can you run the tree command to show the file structure?) Is there any service running that you’d need to proxy to? If you can describe the setup of what you’re trying to serve with as much detail as possible, it’ll be easier to suggest what to try next.

Sure. it is a react application, that runs with react router. It does almost nothing on its own, just connects to a backend java application (thats part 2 :slight_smile: ). /app/build contains a production build, which is build from the following dockerfile:

FROM node:alpine as build
WORKDIR /app
ENV PATH /app/node_modules/bin:$PATH
COPY ./tuui/package.json /app/package.json
RUN npm install
RUN npm install react-scripts
COPY ./tuui/app
RUN npm run build

FROM caddy:2.1.1-alpine
COPY ./Caddyfile /etc/caddy/Caddyfile
COPY --from=build /app /app

With the caddyfile containing for the ui:

handle /* {
    root * /app/build
    file_server
}

it works great. When I go to /ui (by adding that in the handle, then i have issues), then i have issues. it implies that im not able to find my build anymore I think.
Does this help at all, or did I just not provide any new valuable info?

Yes, so like I said, all you should need is this:

root * /app/build

try_files {path} /index.html

file_server

No need for handle

i want it on the /ui route, so that i can have the / route for the proxy though, as like this (copied from documentation):

handle /foo/* {
	file_server
}
handle {
	reverse_proxy 127.0.0.1:8080
}

I dont understand how your suggestion gets it on the /ui route? (unless htats what path is supposed to be?)

So for that you’ll want handle_path instead, because in Caddy, the path is kept, not stripped, by default. handle_path is the same as handle, except that it also strips the prefix from the path before doing the rest.

But you should be aware that you may need to change some configuration on your React app such that the right assets are loaded on every page (there might be a base path configuration option to ensure that /ui/ is prefixed on asset paths).

Why do I have to strip the path? why can the files just be served at /ui, as opposed to /?

Because there’s a difference between the URL path and the filesystem paths. On your filesystem, there’s no /ui directory. Caddy would be looking at /app/build/ui/index.html but that doesn’t exist. So you need to strip /ui from the path before handing it the file server so that it doesn’t look for something that won’t exist.