How to show a page behind HTTP basic auth on a TV without typing the password
A dashboard protected by HTTP authentication is the cheapest private dashboard there is: one line in a reverse proxy and a username and password. The problem was always the screen, which has nobody to type them, and credentials in the URL stopped working years ago. DisplayOps stores the credential encrypted and lets the screen answer the browser's prompt itself. Here is how it works, how to put any page behind basic auth in the first place, and what to check when it does not load.
Updated 2026-09-08 · 9 minute read · Needs DisplayOps agent 0.2.2 or newer, which screens install by themselves.
Why https://user:password@host stopped working
For years the kiosk answer to a password-protected page was to write the username and password into the address. Since 2017 every major browser refuses to send URL credentials for a page's subresources: the top-level page may still load, but its images, scripts, style sheets and data requests get a 401 and the dashboard renders as an empty frame. The address also ends up in logs, browser history, screenshots and support tickets, which is a poor place for a password.
What a browser does support is answering the server's authentication challenge on every request. That is what happens when you type into the small "Sign in" box in a normal browser. DisplayOps does the same thing on the screen: the player watches for the challenge and supplies the stored username and password, for every request to that origin, without the password ever appearing in a URL.
Is your page a fit?
| Your page | How to recognise it | Supported | Notes |
|---|---|---|---|
| HTTP Basic or Digest authentication | The browser itself pops up a small "Sign in" box with username and password fields; the server answers 401 with a WWW-Authenticate header. | Yes | Store the credential on the content item; the screen answers the prompt. |
| Anything behind a reverse proxy you control | Caddy, nginx, Traefik or Apache in front of an internal page, a status board, a public dashboard link. | Yes | Add basic auth at the proxy (step 1) and the page becomes private without touching the tool. |
| A login form on the page | A web page with username and password fields, "Remember me", a Sign in button. Grafana, Home Assistant, Metabase, most SaaS tools. | No | Use the tool's own kiosk or sharing feature instead; see the Grafana and Home Assistant guides. |
| Cookie, bearer token or custom header | The tool wants an Authorization header or a session cookie. | Roadmap | Header and cookie injection are on the roadmap. |
The second row is the useful one: with a reverse proxy in front, any page can be made private this way, including public dashboard links from tools that offer them.
Step 1: put the page behind HTTP basic auth (if it is not already)
If the page already shows the browser's sign-in box, skip to step 2. Otherwise, add basic auth at the reverse proxy in front of it. Create one username per screen or per group of screens, with a long random password; a screen never needs a person's account.
Caddy
Hash the password once (caddy hash-password prompts for it), then protect the site or a path. Caddy 2.8 renamed the directive from basicauth to basic_auth; older versions use the old name.
status.example.com {
basic_auth {
lobby-tv $2a$14$Yu3…hashed-bcrypt-password…
}
reverse_proxy statusboard:3000
} nginx
Create the password file with htpasswd -B -c /etc/nginx/screens.htpasswd lobby-tv (the -B flag uses bcrypt), then:
server {
server_name status.example.com;
location / {
auth_basic "Screens";
auth_basic_user_file /etc/nginx/screens.htpasswd;
proxy_pass http://statusboard:3000;
}
} Serve it over HTTPS. Basic auth sends the password with every request, encoded but not encrypted, so on plain HTTP anyone on the path can read it. Confirm in a private browser window that the page asks for a username and password and loads completely once you enter them.
Step 2: add the page in DisplayOps with its credential
- Create the content. In the portal, Content → + New content, type Website, paste the page's address, for example
https://status.example.com/board?kiosk. Leave the username and password out of the address. - Tick The page asks for a username and password (HTTP authentication) and fill in the username and password you created in step 1. The hint under the fields explains what happens: the password is stored encrypted and sent only to the screen, which answers the browser's login prompt itself, including for the page's own images, scripts and data. It works for Basic and Digest challenges from that address's origin only.
- Save. The content list shows a lock badge on items that carry a credential. Editing the item later shows the username and a blank password field with the placeholder stored; leave blank to keep; type a new password only when it changes.
- Slideshows. Each website item in a slideshow has its own lock button. Use it for the items that need a password; the credential is scoped to that item's origin.
Step 3: assign it and verify
- Assign the content to a display or a group. The screen switches within seconds. If the agent has not yet updated to 0.2.2, the Device card on the display page shows the version; screens check for signed updates on their own and install them without a reflash.
- Take a screenshot from the display page. A fully rendered dashboard, including charts and images, confirms the subresources authenticated as well as the page.
- Watch the Now showing card. If the server rejects the credential, the player reports http auth rejected by https://status.example.com (check the username and password) there. Correct the credential on the content item; every screen using it picks up the change.
How it works under the hood
For readers who need to sign this off:
- At rest: the password is encrypted with the organisation's secrets key (AES-256-GCM) inside the content record. Every API and portal response replaces it with a flag saying a password is stored.
- In transit: the plaintext is decrypted only into the desired state pushed to the screens that show that content, over the same authenticated channel that carries every other command. The device keeps it in a root-only state file. Nothing logs it, and the screen's local pages never receive it.
- On the screen: the player enables the browser's DevTools request interception for the content's origin only (scheme, host and port), with authentication handling on. When the server challenges a request, the player provides the credential once. A second challenge for the same request means the server rejected it, so the request is cancelled and the error is reported instead of retrying forever. Cross-origin frames inside a slideshow are attached and covered the same way.
- Scope: one credential per content item, valid for that item's origin. A page that pulls data from a second protected origin needs that origin behind the same hostname.
- Rotation: the browser keeps a credential that worked until it restarts, so after changing the password on the server, update the content item and send Restart player if you want the switch immediately.
Combining it with Grafana, Home Assistant and others
This feature pairs naturally with tools that can be made anonymous inside a network but should not be public on the internet:
- Grafana: put an anonymous viewer org or the auth proxy behind a basic-auth route on your proxy. Screens anywhere on the internet can now show a private Grafana dashboard with variables and full features, and the proxy's allow-list is no longer needed.
- Public dashboard links from Grafana, Metabase or Power BI can be fronted by your own proxy with basic auth, which turns "anyone with the link" into "anyone with the password".
- Home Assistant is the exception: it has its own login form, and basic auth in front of it does not remove that form. Use trusted networks instead.
- Internal status pages, Uptime Kuma status pages, wiki pages, camera web UIs that show basic auth prompts work as they are.
Doing it from the API
The API takes the same shape the portal uses. Create a website content item with a credential:
curl -X POST https://app.simpledisplayops.com/api/v1/content \
-H "Authorization: Bearer sdo_live_…" -H "Content-Type: application/json" \
-d '{"name": "Lobby status board", "type": "website",
"config": {"url": "https://status.example.com/board?kiosk",
"auth": {"type": "basic", "username": "lobby-tv", "password": "…"}}}' Responses return "auth": {"type": "basic", "username": "lobby-tv", "has_password": true}, never the password. Sending an update with the password omitted keeps the stored one; sending auth with an empty username removes the credential. Slideshow items carry the same auth object per website item.
Troubleshooting
- The screen shows the browser's sign-in box or an empty page. The agent is older than 0.2.2 (check the Device card), or the content item has no credential ticked. Screens update themselves on their next check; the Device card shows the version.
- "http auth rejected by …" on the Now showing card. The server refused the username and password. Test them in a private browser window; check for trailing spaces and for a realm that expects a different user.
- The page loads but charts or images are missing. Those requests go to a different origin than the page (another hostname, or http instead of https, or a different port). Serve them through the same hostname, or remove protection from the asset host.
- It worked, then broke after a password change. Update the credential on the content item, then send Restart player so the browser drops the old one.
- A slideshow item stays blank. The site refuses to be shown in a frame (an X-Frame-Options or frame-ancestors header). Show it as its own website content instead of a slideshow item, or relax the header at your proxy for the screens' origin.
- Proxy authentication (407). Credentials for an HTTP proxy in front of the screen are not supported; this feature is for the web server's own challenge.
Security checklist
- HTTPS on the protected page; basic auth on plain HTTP is readable on the wire.
- One username per screen or group, with a long random password, never a person's own account.
- Bcrypt-hashed password files at the proxy (
caddy hash-password,htpasswd -B). - Credentials only ever entered in the content item, never in the address.
- Rotate at the proxy, then update the item and restart the player; delete the proxy user when a screen is retired.
- Editors in your DisplayOps org can change the URL a credential is sent to only within the same origin, so keep viewer-only roles for people who should not manage content.
Basic auth on a screen: questions we get
Why not just put the username and password in the URL?
Browsers stopped sending URL credentials for a page's images, scripts and data requests in 2017, so a dashboard loads but its charts do not, and the address with the password inside ends up in logs, history and screenshots. DisplayOps answers the browser's own authentication challenge instead, for every request to that origin, and never places the password in the address.
Where is the password stored?
Encrypted in the DisplayOps database with the organisation's secrets key. The portal and the API never return it, only whether one is stored. The plaintext travels once, inside the desired state, over the authenticated channel to the screens that show that content, where it is kept in a root-only file and used to answer the prompt.
Does it work for Digest authentication?
Yes. The browser handles the challenge scheme; the screen only supplies the username and password when asked. Basic and Digest are both covered. NTLM and Kerberos are not tested.
Can one content item hold credentials for two different servers?
No. A credential belongs to the origin of the content item's URL: the scheme, host and port. If the page pulls data from a second protected server, put that server behind the same proxy hostname or drop the protection on it.
I changed the password but the screen keeps working with the old one.
Chromium remembers a credential that was accepted until the browser restarts. Send Restart player from the display page, or wait for the next scheduled reload with a fresh challenge from the server, and the new password takes over.
Which screens support this?
Any DisplayOps screen running agent 0.2.2 or newer. Screens update themselves; the version is shown in the Device card on the display page.
Related
Private dashboard, public screen, no password on a sticky note.
Three screens free. Flash, pair, paste the URL, tick the box.