Automatically login to Wi-Fi captive portals
Find a file
2025-04-16 00:13:53 +07:00
LICENSE Initial commit 2025-04-15 15:57:01 +00:00
README.md Add contents 2025-04-16 00:13:53 +07:00

captiveportal-autologin

Imagine you have a smart device with sensors in your apartment, such as a single-board computer with room temperature and humidity sensors, and sending data somewhere such as an MQTT gateway, cloud server, or your self-hosted Home Assistant instance.

But after some time, the apartment network thinks you're idle and kicks you out because of inactivity, making the smart device lose internet connection. When you're back, you found the smart device stuck at the captive portal login screen (if the device even has one to begin with).

Here's what our example network looks like:

 +-----------------+      
 |Apartment        |      
 |Access Point     |<-+   
 |connected to the |  |   
 |RADIUS server,   |  |   
 |Router, ISP      |  |   
 +-----------------+  |   
+---------------------+--+
|  An apartment room  |  |
|                     |  |
|                     |  |
|             +-------+-+|
|             |Your     ||
|             |Wireless ||
|             |Router   ||
|             +---+-----+|
| +--------+      |      |
| |SBC     |      |      |
| |e.g. RPi+------+      |
| +--------+             |
+------------------------+

This script, whether ran at the SBC or your wireless router (such as on routers running OpenWRT), will make the apartment network think a real device (such as your laptop) actually logged in, keeping the SBC's connection alive.

Setup

You'll need to know what your browser actually sent when you used the login page. For example, our test network's login form sent a HTTP POST containing following payload to the auth server to when clicking the login button:

txtLogin=room1234&txtPasswd=5678&chkRemember=1&btnLogin=Login&reqUrl=http%3A%2F%2Fwww.google.co.th&reqCheck=false

Let's make it a little more human readable

txtLogin=room1234
txtPasswd=5678
chkRemember=1
btnLogin=Login
reqUrl=http://www.google.co.th
reqCheck=false

You can use your browser's dev tools, in the network tab, to see the payload being sent. Adjust you payload accordingly.

Now use curl to trick the network to think a browser is submitting a captive portal login form

URL="captive-portal-url"
PAYLOAD="Login=${USERNAME}&Passwd=${PASSWORD}&chkRemember=1&btnLogin=Login&reqUrl=http%3A%2F%2Fwww.google.co.th&reqCheck=false"

# Spoof a normal-looking user agent
USER_AGENT="User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
curl -X POST "$URL" -d "$PAYLOAD" -H "$USER_AGENT"

One-liner example:

curl -X POST http://<portal-url>/portal/user-authen.php? -d "txtLogin=room1234&txtPasswd=5678&chkRemember=1&btnLogin=Login&reqUrl=http%3A%2F%2Fwww.google.co.th&reqCheck=false" -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"

Cronjob example

Now put it into a cronjob on the SBC or the router (at least on OpenWRT). Make sure curl is installed. This will run our one-liner every 15 minutes.

*/15 * * * * curl -X POST http://<portal-url>/portal/user-authen.php? -d "txtLogin=room1234&txtPasswd=5678&chkRemember=1&btnLogin=Login&reqUrl=http%3A%2F%2Fwww.google.co.th&reqCheck=false" -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"

systemd timer example

We'll need 3 files.

In login-captive-portal.sh:

#!/bin/bash

USERNAME=room1234
PASSWORD=5678

URL="http://<portal-url>"

# Use browser devtools to see what is being sent when the login button is pressed when logging in via web browser
PAYLOAD="Login=${USERNAME}&Passwd=${PASSWORD}&chkRemember=1&btnLogin=Login&reqUrl=http%3A%2F%2Fwww.google.co.th&reqCheck=false"

# Spoof a normal-looking user agent
USER_AGENT="User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"

curl -X POST "$URL" -d "$PAYLOAD" -H "$USER_AGENT"

In login-captive-portal.service:

[Unit]
Description=Autologin to captive portal

[Service]
Type=oneshot

# Only run when connected to wifiSSID which "nmcli | grep wifiSSID" will return 0
ExecCondition=sh -c "nmcli | grep wifiSSID"
ExecStart=/path/to/login-captive-portal.sh


[Install]
WantedBy=multi-user.target default.target

In login-captive-portal.timer:

[Unit]
Description=Connect to captive portal

[Timer]
# Adjust according to your needs
OnCalendar=hourly

[Install]
WantedBy=default.target

Put the login-captive-portal.sh in anywhere such as ~/.local/bin or /usr/local/bin. Make sure to point the ExecStart= line in login-captive-portal.service to it.

Put the login-captive-portal.service and login-captive-portal.timer in the correct systemd service file locations, such as ~/.config/systemd/system for your user or /etc/systemd/system for system-wide.