Skip to content

New hotspot - pfSense

The following describes how to configure a pfSense firewall (version 2.7.2, Community Edition) and how to connect it to our hotspot solution SyCes.

For the installation of pfSense Community Edition (pfSense CE) or pfSense Plus software, the official documentation Netgate Docs is recommended.

Prerequisites

To configure a pfSense, you need administrator access to it. This tutorial demonstrates the configuration via the pfSense GUI web interface.

For the configuration, you also need information about the RADIUS IP and the secret of the new system. This information can be retrieved from SyCes🡕.

To set up the router for SyCes, the location managing this router must exist in the new database. The tenant ID and the token created for the location are required for the configuration. If you want to retrieve these values, you need valid credentials for SyCes.

Navigate to the location page of the corresponding tenant and click on the Copy button next to the Token in the Token field on the Location Details card. The link in the URL field should look like this: /login/<tenant_id>/<location_id>/.

Step 1: Set up the network interface

In the Interfaces > Assignment tab, an interface needs to be configured first. The corresponding interface of the device should be equipped with a Wireless Access Point (WAP) so that customer devices can connect via the interface after configuration.

Image - Network Interface 1

After clicking on Add, a new interface is added. Then, the interface settings must be saved with Save. Afterwards, the interface can be further configured by clicking on its name.

Image - Netzwerk Interface 1

Enable the interface and optionally give it a descriptive name, e.g. GUESTNET. Set the IPv4 configuration to Static IPv4 and enter an IP address (e.g. 192.168.5.1) with a suitable network mask (e.g. /24).
Example interface settings can be seen in the following image:

Image - Interface Einstellungen

Afterwards, the settings must be saved with Save and applied with Apply Changes.

Step 2: Set up the DHCP-Server

The DHCP server must now be configured for the newly created interface. This can be done in Services > DHCP Server. If ISC DHCP is still set as the DHCP Backend (factory settings), it is recommended to switch the DHCP backend to DHCP under System > Advanced > Networking.

Select the created interface in the Services > DHCP Server tab.

Image - Services > DHCP Server

There, the DHCP server must be activated by clicking on the check mark. Then set the DHCP range for the IP addresses needed. Optionally, the DNS server can be set to 1.1.1.1 (Cloudflare) or 8.8.8.8 (Google).

Image - DHCP Einstellungen

Save the settings with Save and apply them with Apply Changes.

Step 3: Create Firewall Rules

In the next step, firewall rules must be created to allow access to the Captive Portal.

To do this, navigate to Firewall > Rules.

Image - Firewall > Rules

Select the interface created in Step 2. Add a new firewall rule by clicking on Add. This rule is used to allow access to the Captive Portal.

Image - Add Firewall Rule

Set the rule to Pass, the protocol to TCP, the source to Interface Subnet, the destination to Interface Address, and enter the range 8000 to 10000 in the Destination port range field. This means that traffic on all ports between 8000 and 10000 is allowed. In practice, however, usually only port 8000 is needed for the Captive Portal.

Optionally, logging can be enabled to monitor traffic and diagnose potential problems.

Image - Firewall Regel für das Captive Portal

Save the settings with Save and apply them with Apply Changes.

Now add another rule to allow internet access for hotspot users. This rule must also be set to Pass, the interface to the new interface (e.g. GUESTNET), the protocol to Any, the source to Interface subnets, and the destination to Any. Optionally, you can add a description to this rule.

Image - Firewall Regel für Gastzugang

Save the settings with Save and apply them with Apply Changes.

Step 4: Set up the RADIUS Server

In this section, the RADIUS server is configured to the SyCes2 web server. First, it must be created in the User Manager under System > User Manager in the Authentication Servers tab.

Image - System > User Manager

...

Image - Select Authentication Servers in User Manager

Click on Add to add an authentication server.

Image - Add authentication server

Give the authentication server a name and select RADIUS as the Type. Additionally, you need to specify the RADIUS IP and the RADIUS Secret. This information can be found on SyCes.

Image - RADIUS Server Configuration

Save your entries by clicking Save.

Step 5: Set up the Captive Portal

Navigate to Services > Captive Portal to set up the Captive Portal.

Image - Services > Captive Portal

Add a new Captive Portal Zone by clicking Add.

Image - Add Captive Portal

Enter a name for the Captive Portal Zone (e.g. GUESTNET). Optionally, you can enter a description for the zone. When done, click Save & Continue.

Image - Enter a name for the Captive Portal, then Save and Continue

Now activate the Captive Portal, select the GUESTNET interface and set a Idle timeout (e.g. 5 minutes).

Image - Activate Captive Portal ...

Image - Set Idle timeout

Scroll to the Authentication section, choose Use an Authentication backend as the authentication method and select the server added in the previous step under Authentication Server.

Image - Select RADIUS Server as authentication method

Now scroll to the Accounting section, enable sending RADIUS accounting packets and select the configured RADIUS server as Accounting Server. Select No updates or Interim updates, activate idle timeout and save the settings by clicking Save.

Image - Set RADIUS Accounting Server

Step 6: Testing the Captive Portal

Connect a device to the network set up in the previous steps and open a webpage in a browser to test the Captive Portal. Now, the Captive Portal should appear, allowing you to log in with your credentials.

The standard pfSense Captive Portal should look like this:

Image - pfSense Captive Portal

If you encounter any issues or unexpected behavior during the configuration of your router, please contact us at [email protected].

Step 7: Customizing the local Captive Portal (optional)

PfSense offers the option to upload a user-friendly portal page instead of using the predefined Captive Portal. To do this, edit the zone configured in Step 5 under Services > Captive Portal.

Image - Services > Captive Portal ...

Image - Edit Captive Zone

Ensure that the checkbox for Enable Captive Portal is selected. Then scroll to the Use custom captive portal page option and enable the checkbox there as well. You can upload an HTML file via Browse. Alternatively, you can use the webpage code provided below to set up a suggested page. Use View Page Contents to preview the rendered page.

Image - Custom Captive Portal Upload

Other pages of the Captive Portal, as well as logos, can also be customized in this tab. Example of a custom Login Page are provided in the following section.

To allow hotspot devices to create user accounts on SyCes, the URL for the SyCes backend must be made accessible before authentication. Navigate to Services > Captive Portal click on the Allowed Hostnames tab and then click Add to add a new entry. Enter backend.syces.de under Hostname. Optionally, you can add a description for the entry. The rest of the settings can remain at their default values. Save your changes by clicking Save.

Image - Add Allowed Hostname

Login Page

Below, you will find the website code for the customized login page described above. You will also find the code for pages that use only the Anonymous or Login function. The German version of the login page is available under Neuer Hotspot - pfSense

This code can be copied for functionality, however, certain fields must still be replaced.

  • <tenant_id> Replace this with your tenant's ID in SyCes
  • <location_identification_token> Replace this with the token of the configured Location in SyCes with router type pfSense. This is required for the Anonymous function.
  • const tenant_domain = ""; Set variable to enable automatic appending of the tenant domain to the username before login. Doing so enables the login with "account_name" instead of "account_name@tenant_domain". The domain can be copied from tenant page in SyCes.
<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>SyCes® - Hotspot</title>
    <link rel="stylesheet" href="https://backend.syces.de/static/login/css/styles.css">
  </head>
  <body>
    <div class="message-container">
      <div>
        <img
          src="https://backend.syces.de/static/login/img/logo2.png"
          alt="SyCes-Logo"
          width="500"
          height="139"
          class="img"
        />
      </div>
      <div class="center window">
        <h1>User Login</h1>
        <form id="default_login" action="$PORTAL_ACTION$" method="post">
          <input name="redirurl" type="hidden" value="$PORTAL_REDIRURL$" />
          <input name="zone" type="hidden" value="$PORTAL_ZONE$" />
          <input name="accept" type="hidden" value="Continue" />
          <table>
            <tr>
              <td><label for="pf_un">Username:</label></td>
              <td><input name="auth_user" type="text" id="pf_un" /></td>
            </tr>
            <tr>
              <td><label for="pf_pd">Password:</label></td>
              <td><input name="auth_pass" type="password" id="pf_pd" /></td>
            </tr>
            <tr>
              <td>
                <label for="tos_check_login"
                >I agree to the
                <a href="https://backend.syces.de/login/<tenant_id>/tos/"
                  >Terms and conditions</a
                >:</label
                >
              </td>
              <td>
                <label class="checkbox">
                  <input
                    type="checkbox"
                    name="tos"
                    id="tos_check_login"
                    onclick="handleTOSClick(this)"
                  />
                  <span class="checkmark"></span>
                </label>
              </td>
            </tr>
            <tr>
              <td></td>
              <td>
                <button
                  class="primary"
                  type="submit"
                  id="submit_btn_login"
                  disabled
                >
                Submit
                </button>
              </td>
            </tr>
          </table>
        </form>
      </div>
      <div class="center window">
        <h1>Register anonymously</h1>
        <form id="anon_login">
          <table>
            <tr>
              <td>
                <label for="tos_check_reg">
                I agree to the
                <a href="https://backend.syces.de/login/<tenant_id>/tos/"
                  >Terms and conditions</a
                >:
                </label>
              </td>
              <td>
                <label class="checkbox">
                <input
                  type="checkbox"
                  name="tos"
                  id="tos_check_reg"
                  onclick="handleTOSClick(this)"
                />
                <span class="checkmark"></span>
                </label>
              </td>
            </tr>
            <tr>
              <td></td>
              <td>
                <button
                  class="primary"
                  type="submit"
                  id="submit_btn_reg"
                  disabled
                >
                Submit
                </button>
              </td>
            </tr>
          </table>
        </form>
      </div>
    </div>
  </body>
  <script>
    const url =
    "https://backend.syces.de/login/token_login/<location_identification_token>/";

    const anon_form = document.getElementById("anon_login");
    const default_form = document.getElementById("default_login");

    const tos_checkbox_reg = document.getElementById("tos_check_reg");
    const tos_checkbox_login = document.getElementById("tos_check_login");
    const submit_button_reg = document.getElementById("submit_btn_reg");
    const submit_button_login = document.getElementById("submit_btn_login");

    function handleTOSClick(cb) {
      if (cb.checked === true) {
        tos_checkbox_reg.checked = true;
        tos_checkbox_login.checked = true;
        submit_button_reg.disabled = false;
        submit_button_login.disabled = false;
      } else {
        tos_checkbox_reg.checked = false;
        tos_checkbox_login.checked = false;
        submit_button_reg.disabled = true;
        submit_button_login.disabled = true;
      }
    }

    function sendRequest() {
      fetch(url)
      .then((response) => response.json())
      .then((data) => {
        let field_username = document.getElementById("pf_un");
        let field_password = document.getElementById("pf_pd");
        field_username.value = data.login_name;
        field_password.value = data.password;
        default_form.submit();
      })
      .catch((error) => console.error(error));
    }

    function appendDomainIfMissing() {
      const tenant_domain = "";
      let form_input_user = document.getElementById("ft_un");
      let username = form_input_user.value;

      if (
        tenant_domain !== "" &&
        !username.includes("@") &&
        !username.endsWith("@" + tenant_domain)
      ) {
        form_input_user.value = username + "@" + tenant_domain;
      }
    }

    anon_form.addEventListener("submit", function (e) {
      e.preventDefault();
      sendRequest();
    });

    default_form.addEventListener("submit", function (e) {
      appendDomainIfMissing();
    });
  </script>
</html>
<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>SyCes® - Hotspot</title>
    <link rel="stylesheet" href="https://backend.syces.de/static/login/css/styles.css">
  </head>
  <body>
    <div class="message-container">
      <div>
        <img
          src="https://backend.syces.de/static/login/img/logo2.png"
          alt="SyCes-Logo"
          width="500"
          height="139"
          class="img"
        />
      </div>
      <div class="center window">
        <h1>User Login</h1>
        <form id="default_login" action="$PORTAL_ACTION$" method="post">
          <input name="redirurl" type="hidden" value="$PORTAL_REDIRURL$" />
          <input name="zone" type="hidden" value="$PORTAL_ZONE$" />
          <input name="accept" type="hidden" value="Continue" />
          <table>
            <tr>
              <td><label for="pf_un">Username:</label></td>
              <td><input name="auth_user" type="text" id="pf_un" /></td>
            </tr>
            <tr>
              <td><label for="pf_pd">Password:</label></td>
              <td><input name="auth_pass" type="password" id="pf_pd" /></td>
            </tr>
            <tr>
              <td>
                <label for="tos_check_login">
                  I agree to the
                  <a href="https://backend.syces.de/login/<tenant_id>/tos/">
                    Terms and conditions
                  </a>:
                </label>
              </td>
              <td>
                <label class="checkbox">
                  <input
                    type="checkbox"
                    name="tos"
                    id="tos_check_login"
                    onclick="handleTOSClick(this)"
                  />
                  <span class="checkmark"></span>
                </label>
              </td>
            </tr>
            <tr>
              <td></td>
              <td>
                <button
                  class="primary"
                  type="submit"
                  id="submit_btn_login"
                  disabled
                >
                  Submit
                </button>
              </td>
            </tr>
          </table>
        </form>
      </div>
    </div>
  </body>
  <script>
    const default_form = document.getElementById("default_login");

    const tos_checkbox_login = document.getElementById("tos_check_login");
    const submit_button_login = document.getElementById("submit_btn_login");

    function handleTOSClick(cb) {
      if (cb.checked === true) {
        tos_checkbox_login.checked = true;
        submit_button_login.disabled = false;
      } else {
        tos_checkbox_login.checked = false;
        submit_button_login.disabled = true;
      }
    }

    function appendDomainIfMissing() {
      const tenant_domain = "";
      let form_input_user = document.getElementById("ft_un");
      let username = form_input_user.value;

      if (
        tenant_domain !== "" &&
        !username.includes("@") &&
        !username.endsWith("@" + tenant_domain)
      ) {
        form_input_user.value = username + "@" + tenant_domain;
      }
    }

    default_form.addEventListener("submit", function (e) {
    appendDomainIfMissing();
    });
  </script>
</html>
<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>SyCes® - Hotspot</title>
    <link rel="stylesheet" href="https://backend.syces.de/static/login/css/styles.css">
  </head>
  <body>
    <div class="message-container">
      <div>
        <img
          src="https://backend.syces.de/static/login/img/logo2.png"
          alt="SyCes-Logo"
          width="500"
          height="139"
          class="img"
        />
      </div>
      <div class="center window">
        <h1>Register anonymously</h1>
        <form id="anon_login">
          <table>
            <tr>
              <td>
                <label for="tos_check_reg">
                  I agree to the
                  <a href="https://backend.syces.de/login/<tenant_id>/tos/">
                    Terms and conditions
                  </a>:
                </label>
              </td>
              <td>
                <label class="checkbox">
                  <input
                    type="checkbox"
                    name="tos"
                    id="tos_check_reg"
                    onclick="handleTOSClick(this)"
                  />
                  <span class="checkmark"></span>
                </label>
              </td>
            </tr>
            <tr>
              <td></td>
              <td>
                <button
                  class="primary"
                  type="submit"
                  id="submit_btn_reg"
                  disabled
                >
                  Submit
                </button>
              </td>
            </tr>
          </table>
        </form>
        <form
          id="default_login"
          action="$PORTAL_ACTION$"
          method="post"
          style="display:none"
        >
          <input name="redirurl" type="hidden" value="$PORTAL_REDIRURL$" />
          <input name="zone" type="hidden" value="$PORTAL_ZONE$" />
          <input name="accept" type="hidden" value="Continue" />
          <h1>
            Login
          </h1>
          <table>
            <tr>
              <td><label for="pf_un">Username:</label></td>
              <td><input name="auth_user" type="text" id="pf_un" /></td>
            </tr>
            <tr>
              <td><label for="pf_pd">Password:</label></td>
              <td><input name="auth_pass" type="password" id="pf_pd" /></td>
            </tr>
            <tr>
              <td>
                <label for="tos_check_login"
                >I agree to the
                <a href="https://backend.syces.de/login/<tenant_id>/tos/"
                  >Terms and conditions</a
                >:</label
                >
              </td>
              <td>
                <label class="checkbox">
                  <input
                    type="checkbox"
                    name="tos"
                    id="tos_check_login"
                    onclick="handleTOSClick(this)"
                  />
                  <span class="checkmark"></span>
                </label>
              </td>
            </tr>
            <tr>
              <td></td>
              <td>
                <button
                  class="primary"
                  type="submit"
                  id="submit_btn_login"
                  disabled
                >
                Submit
                </button>
              </td>
            </tr>
          </table>
        </form>
      </div>
    </div>
  </body>
  <script>
    const url =
    "https://backend.syces.de/login/token_login/<location_identification_token>/";

    const anon_form = document.getElementById("anon_login");
    const default_form = document.getElementById("default_login");

    const tos_checkbox_reg = document.getElementById("tos_check_reg");
    const tos_checkbox_login = document.getElementById("tos_check_login");
    const submit_button_reg = document.getElementById("submit_btn_reg");
    const submit_button_login = document.getElementById("submit_btn_login");

    function handleTOSClick(cb) {
      if (cb.checked === true) {
        tos_checkbox_reg.checked = true;
        tos_checkbox_login.checked = true;
        submit_button_reg.disabled = false;
        submit_button_login.disabled = false;
      } else {
        tos_checkbox_reg.checked = false;
        tos_checkbox_login.checked = false;
        submit_button_reg.disabled = true;
        submit_button_login.disabled = true;
      }
    }

    function sendRequest() {
      fetch(url)
      .then(response => response.json())
      .then(data => {
        let field_username = document.getElementById("pf_un");
        let field_password = document.getElementById("pf_pd");
        field_username.value = data.login_name;
        field_password.value = data.password;
        default_form.submit();
        }
      )
      .catch(error => console.error(error));
    }

    anon_form.addEventListener("submit", function(e) {
      e.preventDefault();
      sendRequest();
    });
  </script>
</html>

Use Restore Default Page to reset settings back to the predefined Captive Portal configuration.