Implementing a Captive portal using Apache
I have been trying to build a captive portal in Apache that I plan to be Apple CNA aware.
I found several relevant posts in StackOverflow, including Captive portal popups… and How to create WiFi popup login page.
I defined the relevant Apache configuration as:
RewriteEngine on
RewriteCond %{HTTP_USER_AGENT} ^CaptiveNetworkSupport(.*)$ [NC]
RewriteRule ^(.*)$ http://192.168.2.1/captive/portal.html [L,R=302]
# android
RedirectMatch 302 /generate_204 http://192.168.2.1/captive/portal.html
# windows
RedirectMatch 302 /ncsi.txt http://192.168.2.1/captive/portal.html
It is not working quite right, as the CNA browser enters a redirect loop.
I also tried putting all my relevant pages into a /captive directory, and defining the rule
RewriteRule !^captive($|/) http://192.168.2.1/captive/portal.html [L,R=302]
But had similar loop problems. What to do?
Upon investigating and doing some tests, it is evident the Apple CNA is a web browser of its own; evidently if an exception is not properly made, all subsequent requests will have yet again the same user agent. So it will start the procedure/portal redirection from scratch, thus the redirect loops.
So in the rule for Apple, we won’t redirect anymore if the destination host is the captive portal server.
# apple
RewriteEngine on
RewriteCond %{HTTP_USER_AGENT} ^CaptiveNetworkSupport(.*)$ [NC]
RewriteCond %{HTTP_HOST} !^192.168.2.1$
RewriteRule ^(.*)$ http://192.168.2.1/captive/portal.html [L,R=302]
# android
RedirectMatch 302 /generate_204 http://192.168.2.1/captive/portal.html
# windows
RedirectMatch 302 /ncsi.txt http://192.168.2.1/captive/portal.html
We also add a generic catch-all rule here, that if none of the previous conditions happen, or we are dealing with a OS which we do not have a rule for, it will redirect to the portal if not already there (e.g. not visiting the captive directory).
RewriteEngine on
RewriteCond %{REQUEST_URI} !^/captive/ [NC]
RewriteRule ^(.*)$ http://192.168.2.1/captive/portal.html [L]
Obviously, I would stress out that with this configuration, all the captive portal specific files have to live below the /captive directory.