Guide · HTTP authentication

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-089 minute readNeeds 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 Chromium, the browser on the screen, 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.

Before you start

  • A page that shows the browser's sign-in box, or a reverse proxy you can add one to.
  • One username and a long random password per screen or group.
  • HTTPS on the page if screens reach it over the internet.
  • A paired DisplayOps screen; three are free.

When you are done

  • The page is private to whoever knows the password.
  • The screen signs in by itself, for the page and everything it loads.
  • The password lives encrypted on the content item, never in a URL.
  • A wrong password shows up as a message in the portal, not a blank TV.

Is your page a fit?

Your pageHow to recognise itSupportedNotes
HTTP Basic or Digest authenticationThe browser itself pops up a small "Sign in" box with username and password fields; the server answers 401 with a WWW-Authenticate header.YesStore the credential on the content item; the screen answers the prompt.
Anything behind a reverse proxy you controlCaddy, nginx, Traefik or Apache in front of an internal page, a status board, a public dashboard link.YesAdd basic auth at the proxy (step 1) and the page becomes private without touching the tool.
A login form on the pageA web page with username and password fields, "Remember me", a Sign in button. Grafana, Home Assistant, Metabase, most SaaS tools.NoUse the tool's own kiosk or sharing feature instead; see the Grafana, Metabase and Home Assistant guides.
Cookie, bearer token or custom headerThe tool wants an Authorization header or a session cookie.RoadmapHeader 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 1Put the page behind HTTP basic auth

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.

  1. 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; current releases (2.11) use the new name and still accept it with bcrypt hashes.

    Caddyfile
    status.example.com {
      basic_auth {
        lobby-tv $2a$14$Yu3…hashed-bcrypt-password…
      }
      reverse_proxy statusboard:3000
    }
  2. nginx

    Create the password file with htpasswd -B -c /etc/nginx/screens.htpasswd lobby-tv (the -B flag uses bcrypt), then:

    nginx site config
    server {
      server_name status.example.com;
      location / {
        auth_basic           "Screens";
        auth_basic_user_file /etc/nginx/screens.htpasswd;
        proxy_pass           http://statusboard:3000;
      }
    }
  3. Serve it over HTTPS and test

    Basic auth sends the password with every request, encoded but not encrypted, so on plain HTTP anyone on the path can read it.

    In a private browser window the page asks for a username and password and loads completely once you enter them.

Step 2Add the page in DisplayOps with its credential

  1. Create the content

    In the portal, Content+ New content, type Website, and paste the page's address:

    https://status.example.com/board?kiosk

    Leave the username and password out of the address.

  2. Tick the HTTP authentication option

    Tick The page asks for a username and password (HTTP authentication) and fill in the username and password from 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.

  3. 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.

  4. 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 3Assign it and verify

  1. Assign the content

    Open the display or a group and choose the content. 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.

  2. Take a screenshot

    From the display page. A fully rendered dashboard, including charts and images, confirms the subresources authenticated as well as the page.

  3. 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 state file readable only by the agent's own user. 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, Metabase 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 Metabase, Grafana 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, and so does a page you wrote yourself.

Doing it from the API

The API takes the same shape the portal uses. Create a website content item with a credential:

POST /api/v1/content
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.
  • Viewer-only roles in DisplayOps for people who should not manage content: whoever can edit a content item can change the address its credential is sent to.

Basic auth on a screen: questions we get

Why not just put the username and password in the URL?

Chromium 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 file readable only by the agent 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.

Private dashboard, public screen, no password on a sticky note.

Three screens free. Flash, pair, paste the URL, tick the box.