Meraki guest Wi-Fi (splash page)
This guide sets up guest Wi-Fi on a Cisco Meraki network. The Meraki splash page asks guests for a username and password, and SpherAAA checks them.
Meraki hosts the login page itself. SpherAAA doesn't serve a portal; it only answers the RADIUS requests that Meraki sends when a guest signs in.
How it works
- A guest joins the SSID and gets the Meraki splash page.
- The guest enters a username and password.
- The Meraki access point sends a RADIUS Access-Request to SpherAAA, using PAP.
- PolicyLogic looks the guest up in your
userscollection and compares the password. - SpherAAA sends back Access-Accept or Access-Reject, and Meraki lets the guest on or shows an error.
The RADIUS requests come from the access points, not from the Meraki cloud. That affects which IP address you register in SpherAAA (see Step 1).
Before you start
You'll need:
- Admin access to the Meraki network that runs the guest SSID.
- The public IP address the site's access points use to reach the internet. If the APs sit behind NAT, this is the site's NAT/public IP.
- A SpherAAA environment with an AUTH PolicyLogic script. The default template works as-is for basic guest login.
Step 1: Register the site as a NAS
In SpherAAA, go to Configuration > NAS and create a Client (Host/IP) entry:
- IP Address: the site's public IP, or a CIDR range if the APs use more than one. Every AP behind the same NAT shares one entry.
- Secret: use Generate Password, and keep it for Step 3.
- Environment: the environment whose PolicyLogic should handle guest logins.
See NAS for the full list of fields. If the site's public IP changes (a dynamic ISP connection, for example), read Dynamic or changing NAS IP addresses first.
While you're on the NAS page, note the IP address and Authentication Port SpherAAA listens on. You'll enter them in Meraki.
Step 2: Create guest accounts
Guests are ordinary entries in a collection named users, one entry per guest:
{
"User-Name": "guest-0417",
"User-Password": "sunny-harbor-52"
}
If the collection doesn't exist yet, create it on the Collections page. We recommend:
- an index on
User-Name, so each username is unique and lookups stay fast; User-Passwordas an encrypted field. The dashboard then masks it and only lets you overwrite it, while PolicyLogic still sees the real value.
You can add guests three ways:
- Dashboard: open the collection and use Create entry.
- Bulk upload: upload a JSON file of entries from the collection page. Download example file shows the exact format.
- API:
POST /api/collections/userswith the entry as the body. See API - Collections.
Step 3: Configure the Meraki splash page
In the Meraki dashboard:
- Go to Wireless > Configure > Access control and pick the guest SSID.
- Under Security, choose an open association (no key), so the splash page does the authentication.
- Under Splash page, choose Sign-on with my RADIUS server.
- In the RADIUS servers list that appears, add SpherAAA:
- Host: the SpherAAA IP address from the NAS page.
- Port: the SpherAAA Authentication Port.
- Secret: the secret from Step 1. It must match exactly.
- Save.
Meraki may also offer RADIUS accounting for this SSID. You only need it for recipes that track live sessions; basic guest login works without it.
Step 4: Check PolicyLogic
The default AUTH template already handles guest login. For a plain username/password request, it looks up the User-Name in the users collection, compares User-Password, and accepts on a match.
Test it before trying a real device. Open the script, click Validate, choose your Meraki NAS as the Requested Device, set Authentication Mode to Raw/MAB attributes (no EAP), and send:
{
"User-Name": "guest-0417",
"User-Password": "sunny-harbor-52"
}
You should get Access-Accept. See Testing and validating a script for the rest of the Validate modal.
Then connect a phone or laptop to the guest SSID and sign in on the splash page.
Recipes
The default template covers basic login. The recipes below add common guest-Wi-Fi rules using collections and PolicyLogic. They aren't separate product features, so adapt them to your own script.
All of them build on the checkGuest() function below. Call it from start() in place of the template's plain username/password check.
function checkGuest() {
var result = Collection.get("users", "User-Name", radius.request['User-Name']);
if (!result.ok || result.data['User-Password'] !== radius.request['User-Password']) {
fail("Invalid username or password");
return false;
}
var guest = result.data;
// Recipe checks go here (expiry, device limit, ...).
// Copy only the reply attributes you want Meraki to receive.
if (guest['Session-Timeout']) {
radius.reply['Session-Timeout'] = guest['Session-Timeout'];
}
success();
return true;
}
!!! tip
The default template copies the whole user entry into the reply. SpherAAA only sends attributes it recognises as RADIUS attributes and silently drops the rest, so a custom field like expires does no harm. Copying only the attributes you want, as above, keeps the reply easy to read in the logs.
Expiring guest accounts
Add an expires field to each guest, as an ISO date and time with a timezone. The Z below means UTC:
{
"User-Name": "guest-0417",
"User-Password": "sunny-harbor-52",
"expires": "2026-10-31T23:59:00Z"
}
Then add this to checkGuest() where the recipe checks go:
if (guest['expires'] && Date.now() > Date.parse(guest['expires'])) {
fail("Guest access has expired");
return false;
}
A date written without a timezone, such as 2026-10-31T23:59:00, is read as UTC, not local time. Always include Z or an offset like +02:00 so expiries land when you expect.
To cut the guest off when the account expires, even if they're still connected, cap the session at the time left (see Session length and group policy):
if (guest['expires']) {
var secondsLeft = Math.floor((Date.parse(guest['expires']) - Date.now()) / 1000);
radius.reply['Session-Timeout'] = String(secondsLeft);
}
Vouchers
A voucher is a short, random username/password pair you print or hand out. Generate a batch in a spreadsheet or script, save it as JSON, and bulk-upload it to the users collection (see Step 2):
[
{ "User-Name": "v-7K2Q", "User-Password": "48213", "expires": "2026-10-05T18:00:00Z" },
{ "User-Name": "v-M9TD", "User-Password": "90547", "expires": "2026-10-05T18:00:00Z" }
]
Combine vouchers with expiring accounts so unused ones stop working on their own. Use Download example file on the collection page to get the upload format right.
Device limits
To stop one guest login from being shared across many devices, keep track of which devices each guest has used, and reject new ones past a limit. The PolicyLogic example Cap the number of distinct devices per username already does this. Call its checkDeviceLimit() logic where the recipe checks go, using your guest's User-Name.
For a limit on devices connected at the same time, use Limit concurrent sessions per user instead. That one needs RADIUS accounting turned on in Meraki.
Session length and group policy
You can shape a guest's session with reply attributes:
Session-Timeout(seconds): how long the guest stays signed in before signing in again.Filter-Id: the name of a Meraki group policy to apply to the guest, for example a bandwidth-limited "Guests" policy.
Set them per guest on the user entry (and copy them in checkGuest()), or for everyone in the script:
radius.reply['Session-Timeout'] = "14400"; // 4 hours
radius.reply['Filter-Id'] = "Guests";
SpherAAA sends both attributes in the Access-Accept. On the Meraki side, keep in mind:
- Meraki's Splash frequency setting also controls how often guests sign in again, so set it to match your
Session-Timeout. - The group policy name must match a policy defined in Meraki exactly. In the SSID's access control settings, check which RADIUS attribute Meraki reads the group policy name from (
Filter-Id,Reply-MessageorAirespace-ACL-Name), and send that one.
Troubleshooting
Use the logs under Analytics in the SpherAAA dashboard:
- RADIUS Logs: every request and reply, with attributes (passwords masked), result and timing. Start here.
- Discarded Requests: requests SpherAAA dropped, for example from an unknown NAS.
- System Logs: errors, including PolicyLogic script errors.
- User Logs: output from
log()calls in your script.
| Symptom | Likely cause | What to check |
|---|---|---|
| Splash page times out, nothing in RADIUS Logs | The request comes from an IP SpherAAA doesn't know. | Look in Discarded Requests for the source IP, and compare it with your NAS entry. Remember the requests come from the APs' public/NAT IP, not the Meraki cloud. See also ERR01. |
| Splash page times out, the IP matches | Shared secret mismatch, or the site blocks outbound RADIUS. | Re-enter the secret on both sides (no extra spaces). Make sure the site's firewall allows outbound UDP to the SpherAAA Authentication Port. |
Access-Reject with "User not found" (default template) or "Invalid username or password" (checkGuest()) |
Wrong credentials, or the guest isn't in users. |
Check the entry exists with that exact User-Name (it's case-sensitive), and reset the password if unsure. |
| Access-Reject with "Guest access has expired" | The expires time has passed. |
Check the expires value and its timezone. A missing Z means UTC. |
| Access-Reject with "PolicyLogic error" | The script threw an error. | See System Logs for the error, then test the script in the Validate modal. |
| Guest is signed out sooner or later than expected | Meraki's own splash settings may override Session-Timeout. |
Compare Meraki's Splash frequency with the Session-Timeout in RADIUS Logs. |
| Group policy isn't applied | Meraki may not read the attribute you sent, or the name doesn't match. | Confirm the policy name matches, and that you're sending the attribute Meraki reads the group policy from. |