Solution 1:

...but https://alt.example.org/ (empty path) results in a Location header with the value https://alt.example.org^/login. Wow! What's causing that redirect with the ^ in the domain, and why does it reference alt instead of sub?

The ProxyPassReverse directive does not take a regex as the first argument and this would seem to be where the conflict is happening. And neither can this take ! as the second argument. The alt subdomain would appear in the Location header if the ProxyPassReverse directive did not match the response.

You don't appear to need the first ProxyPassReverse directive that relates to /login, since this URL is not being proxied.

Also, these ProxyPassMatch directives appear to proxy everything to the root of https://sub.example.org/ - do you not want to proxy to the corresponding URL-path at the target domain? Although, strangely, that does appear to be what's happening in your observed results? If you do want to proxy to the same URL-path then you can use the simpler ProxyPass directive instead and use simple prefix-matching as opposed to a regex.

alt.example.org/login redirects to sub.example.org/login

Although that is not performed by the "redirect" you posted at the top, which would redirect to the document root, since the $1 backreference is empty because there is no capturing subpattern in the RewriteRule pattern. As you state later, "the Rails app itself is actually redirecting to https://sub.example.org/login".

So, in order to redirect to sub.example.org/login (which appears to be the intention) then you would need to change the directive to read something like the following instead:

RewriteRule ^/(login)$ https://sub.example.org/$1" [R,L]

The preceding RewriteCond directive is not required here, since that simply repeats the same check you are already performing in the RewriteRule pattern.

RequestHeader set Host sub.example.org

This line is redundant, since this is the purpose of the preceding ProxyPreserveHost off directive.

Header set Host alt.example.org

This line would also seem to be redundant, since Host is a request header, not a response header.

ProxyPassMatch   ^/login ! # Prevent proxy on /login

Also, Apache does not support line-end comments. Only because of a "quirk" in the way Apache directives are processed prevents this particular line-end comment from breaking your server!

So, taking the above points into consideration, try the following instead:

RewriteEngine on

# Externally Redirect "/login" to other domain
RewriteRule ^/(login)$ https://sub.example.org/$1" [R,L]

SSLProxyEngine on

ProxyRequests off
ProxyPreserveHost off

# Prevent proxy on /login
ProxyPass /login !

# Proxy all other URLs
ProxyPass / https://sub.example.org/
ProxyPassReverse / https://sub.example.org/