How to add flespi login to your web page

Add a flespi sign-in to your own web page: users log in with a flespi account or as realm users and the page gets a flespi token without pasting it.

This article explains how to add a flespi sign-in to a web page of your own, a dashboard, a client portal or a one-off tool, so that its users get a flespi token without pasting one. The page opens the flespi login app at https://login.flespi.io in a popup window, the user signs in there, and the login app hands the token back to the page. The page never sees the password. With the token the page calls the flespi REST API and subscribes to the flespi MQTT broker on behalf of the signed-in user.

The complete working example is available on GitHub: flespi-software/auth-example, with a live demo.

Which login to use

The login app has two entry points, and the difference is what token your page receives:

  • flespi account login, https://login.flespi.io/#/login: the user signs in with a flespi account, by email and password or a social provider, and the page gets a master token of that account with a one-hour idle lifetime. It gives full access to the account, so use it for tools made for flespi users themselves.
  • Realm login, https://login.flespi.io/#/realm/REALM_PUBLIC_ID: the user signs in as a user of your realm, with a username and password or an identity provider configured in the realm, and the page gets the user's own token. Its access type, ACL, lifetime and origins come from the realm role, so this is the choice for pages your customers or colleagues open.

Prerequisites

  • A web page served over HTTPS from any host, including a file in a flespi CDN.
  • For the realm login: a realm with its users created, or an identity provider connected so users sign in with their company accounts. See Realms: create and manage users. Note the realm public id shown in the realm settings, the page needs it.
  • Popups allowed for your page. Browsers open a popup only from a user action, so the login must start from a click.

Step 1. Add the login button

The button opens the login app in a popup. For the flespi account login:

<button onclick="flespiLogin()">Login with flespi</button>

<script>
var popup = null

function flespiLogin () {
popup = window.open('https://login.flespi.io/#/login', 'flespi-login', 'width=500,height=650')
}
</script>

For the realm login the popup URL points to your realm. Replace YOUR_REALM_PUBLIC_ID with the public id of the realm:

<button onclick="realmLogin()">Login</button>

<script>
var REALM = 'YOUR_REALM_PUBLIC_ID'
var popup = null

function realmLogin () {
popup = window.open('https://login.flespi.io/#/realm/' + encodeURIComponent(REALM), 'flespi-login',
'width=500,height=650')
}
</script>

If window.open returns nothing, the browser blocked the popup. Ask the user to allow popups for your page.

Step 2. Receive the token

After a successful sign-in the login app sends the token to your page with postMessage. The message is a string:

FlespiLogin|token:{"token": "TOKEN", "region": {"rest": "https://flespi.io", "mqtt-ws": "mqtt.flespi.io:443", "cdn": "https://cdn.flespi.io"}}
  • token is the flespi token. Send it as the Authorization: FlespiToken TOKEN header of REST calls and use it as the username of MQTT connections.
  • region.rest is the REST API host of the user's region and region["mqtt-ws"] the MQTT over WebSocket endpoint, so the page works in every flespi region without hardcoded hosts.

The same listener serves both entry points. It accepts only messages whose event.origin is the login app, takes the token and closes the popup:

<script>
window.addEventListener('message', function (event) {
if (event.origin !== 'https://login.flespi.io' || typeof event.data !== 'string' ||
event.data.indexOf('FlespiLogin|token:') !== 0) {
return
}
var login = JSON.parse(event.data.replace('FlespiLogin|token:', ''))
if (popup && !popup.closed) popup.close()

var token = login.token.replace(/^FlespiToken /, '')
var rest = login.region && login.region.rest || 'https://flespi.io'
// use the token, e.g.:
fetch(rest + '/auth/info', { headers: { Authorization: 'FlespiToken ' + token } })
.then(function (r) { return r.json() })
.then(function (info) { console.log(info.result[0]) })
})
</script>

The message can arrive twice, so store the token and close the popup only once. A legacy second message FlespiToken TOKEN is sent as well and can be ignored. If the login app is opened in a plain browser tab instead of a popup, it has no page to hand the token to and sends the user to the flespi panel.

Step 3. Shape the realm token

For the realm login, the token parameters of the realm role define what the page can do with the token:

  • Set the access to exactly what the page needs: an ACL token limited to the API calls and MQTT topics the page uses, and a lifetime that fits how the page is used.
  • If the role restricts origins, include the origin your page is served from, for example https://cdn.flespi.io for a page hosted in a flespi CDN, next to the {"preset": "flespi"} entry that keeps the login app itself working. Browser calls with a token whose origins do not include your page are rejected. See token origins.

A realm user has one token. Signing in again returns the same token with its lifetime refreshed, so all sessions of one user share it, and deleting that token signs the user out everywhere.

Login form without the popup

A page can show its own login form and call the realm login method directly, with no popup and no flespi UI. Post the user name and password to the realm:

curl -X POST https://flespi.io/realm/REALM_PUBLIC_ID/login \
-H "Content-Type: application/json" \
-d '{"name": "USER_NAME", "password": "PASSWORD"}'

A successful call returns the user's token in result[0].token. An invalid realm id, name or password, or a disabled user, returns 403. The call needs no authorization header and accepts browser requests from any origin.

The password must be sent exactly as it was stored. The flespi panel and the login app hash the typed password with SHA-512 and send the lowercase hexadecimal digest, 128 characters, so for users created or changed through flespi interfaces the form must do the same before posting:

async function sha512hex (text) {
var digest = await crypto.subtle.digest('SHA-512', new TextEncoder().encode(text))
return Array.from(new Uint8Array(digest)).map(function (b) { return b.toString(16).padStart(2, '0') }).join('')
}

Send the raw text only when the password was set as text, through the API or with the text option of the panel. A raw password must be at least 16 characters long, a shorter one is rejected before the credentials are checked.

Security notes

  • Always check event.origin before accepting a message. Any page can post messages to yours.
  • The account login hands the page a master token. Prefer the realm login for anything other users open, and shape the realm role so the token reaches only what the page shows.
  • Keep the token in memory for the session. A page served from a shared origin, such as a CDN host, shares browser storage with every other page on that origin.
  • When the user signs in with a social provider or an identity provider, the login app asks for confirmation before transferring the token to a page outside flespi.io.

See also


See also
The recommended way to authorize external AI agents in flespi: users sign in to a realm and approve MCP access, no token is created or shared.
Configure a realm OAuth server so applications can act on behalf of realm users.