Build your own login page
CloudAuro guide · 5 min read
Before you start#
In your project's Database tab, choose Website accounts - signup and login when creating the database, or enable Website accounts on an existing one. Your project must be published and have active hosting. Accounts created here belong only to this website and are separate from CloudAuro customer accounts.
Create login.html#
Save this complete HTML as login.html. It uses your own form IDs so the SDK does not attach its built-in page handler. You can add your own CSS without changing the form IDs or field names.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sign in to my website</title>
<script src="/__cloudauro/auth.js" defer></script>
<script src="/login.js" defer></script>
</head>
<body>
<main>
<h1>Welcome back</h1>
<form id="custom-login-form">
<p><label>Email <input name="email" type="email"
autocomplete="email" required></label></p>
<p><label>Password <input name="password" type="password"
autocomplete="current-password" required></label></p>
<button type="submit">Sign in</button>
</form>
<section id="signed-in" hidden>
<p id="greeting"></p>
<button id="sign-out" type="button">Sign out</button>
</section>
<p id="login-message" role="status" aria-live="polite"></p>
<a href="/__cloudauro/signup">Create an account</a>
</main>
</body>
</html>Create login.js#
Save this as login.js. The scripts load in order using defer: the CloudAuro SDK is available before your own code runs. No database ID, secret key, or bearer token is needed for website authentication.
"use strict";
const form = document.querySelector("#custom-login-form");
const message = document.querySelector("#login-message");
const signedIn = document.querySelector("#signed-in");
const greeting = document.querySelector("#greeting");
const signOut = document.querySelector("#sign-out");
form.addEventListener("submit", async (event) => {
event.preventDefault();
if (!form.reportValidity()) return;
const submit = form.querySelector('[type="submit"]');
if (submit.disabled) return;
submit.disabled = true;
message.textContent = "Signing in...";
try {
const visitor = await CloudAuroAuth.signIn(
form.elements.email.value,
form.elements.password.value
);
// Render visitor text safely; do not use innerHTML.
greeting.textContent = "Hello, " + visitor.name + "!";
form.reset();
form.hidden = true;
signedIn.hidden = false;
message.textContent = "You are signed in.";
} catch (error) {
message.textContent = error.message;
} finally {
submit.disabled = false;
}
});
signOut.addEventListener("click", async () => {
if (signOut.disabled) return;
signOut.disabled = true;
try {
await CloudAuroAuth.signOut();
signedIn.hidden = true;
form.hidden = false;
message.textContent = "You have signed out.";
} catch (error) {
message.textContent = error.message;
} finally {
signOut.disabled = false;
}
});Upload and test#
In Files, use Add or update files and upload BOTH login.html and login.js with the destination folder left blank. This puts the files at /login.html and /login.js. Open your published website's /login route (or /login.html), not the CloudAuro dashboard address and not a file:// URL. If you use Routes to add the HTML separately, upload login.js through Files at the root too. Do not choose Replace entire website unless the upload contains the entire site, including index.html.
Use Create an account to open the provided signup page. Create a test visitor account with a password of 12-128 characters, then return to your custom login page and sign in. A successful login displays the visitor's name; Sign out ends the session. Incorrect passwords display the API error. Login returns {id, email, name}; passwords and session tokens are never returned to your JavaScript.
Check the session on another page#
Add an element such as <p id="welcome"></p> to that page and load /__cloudauro/auth.js before this code runs. The browser sends the site's HttpOnly cookie automatically. A missing or expired session causes me() to throw. This example changes the visible page only; it does not secure static files or private table data.
try {
const visitor = await CloudAuroAuth.me();
document.querySelector("#welcome").textContent = `Hello, ${visitor.name}`;
} catch (error) {
document.querySelector("#welcome").textContent = "Please sign in.";
}Use your own signup form too#
Add name, email, and password fields to your own form and call this from its submit handler, after preventDefault() and validation:
try {
const visitor = await CloudAuroAuth.signUp({
name: signupForm.elements.name.value,
email: signupForm.elements.email.value,
password: signupForm.elements.password.value
});
statusElement.textContent = `Account created for ${visitor.name}`;
} catch (error) {
statusElement.textContent = error.message;
}Direct API alternative#
If you do not use the SDK, the equivalent login request is:
const response = await fetch("/__cloudauro/auth/login", {
method: "POST",
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
"X-CloudAuro-Auth": "1"
},
body: JSON.stringify({ email, password })
});
const result = await response.json();
if (!response.ok) throw new Error(result.error?.message || "Sign-in failed");
const visitor = result.data;The browser supplies Origin automatically; do not set it yourself. Login/signup/logout/password changes use POST. GET /__cloudauro/auth/me reads the current visitor. POST /__cloudauro/auth/password requires currentPassword and newPassword. DELETE /__cloudauro/auth/me requires currentPassword. Mutations require the same website origin, JSON, and X-CloudAuro-Auth: 1. Call these relative endpoints on your published project address, not the CloudAuro management address. Do not put credentials into URLs, logs, or localStorage.
Important limits#
Sessions last 12 hours. Visitor email verification and forgotten-password recovery are not available yet. Emails are self-reported. Signed-in visitors can change their password, and project owners can delete visitor accounts. A login page is not access control for public HTML/CSS/JS: private content requires server-side authorization that is not provided by the static hosting feature. Website authentication does not authorize database-table requests. Never expose write/delete bearer tokens in this example.