Auth
Configure the user authentication
LumeCMS implements the 'Basic' HTTP Authentication. Use the function auth with an object with the usernames and passwords:
cms.auth({
user1: "password1",
user2: "password2",
});
If you don't want to have the passwords visible in the configuration file, you can use environment variables:
const user = Deno.env.get("CMS_USERNAME");
const password = Deno.env.get("CMS_PASSWORD");
cms.auth({
[user]: password,
});
Custom auth provider
For advanced use cases, you can implement your own authentication method by passing a custom AuthProvider as the second argument to cms.auth().
An AuthProvider is an object implementing the following interface:
interface AuthProvider {
/** Called once on startup with the user map and base path */
init(
options: { basePath: string; users: Map<string, UserConfiguration> },
): void;
/** Called on every request. Return the matched username on success,
* or a Response (e.g. a challenge or redirect) to deny access. */
login(request: Request): Response | string | Promise<Response | string>;
/** Called when the user triggers the logout action */
logout(request: Request): Response | Promise<Response>;
/** Handles requests under the /auth/* path (e.g. form submissions or OAuth callbacks) */
fetch(request: Request): Response | Promise<Response>;
}
Example: login form
The following example replaces the Basic Auth browser dialog with a simple HTML login form. It uses a cookie to keep the session alive — no external dependencies required:
cms.auth(
{ admin: { password: "secret" } },
{
init(options) {
this.options = options;
},
login(request) {
const cookie = request.headers.get("cookie") ?? "";
const user = cookie.match(/session=([^;]+)/)?.[1];
if (user && this.options.users.has(user)) return user;
const basePath = this.options.basePath === "/" ? "" : this.options.basePath;
return new Response(
`<form method="POST" action="${basePath}/auth/login">
<input name="user" placeholder="Username" />
<input name="password" type="password" placeholder="Password" />
<button>Login</button>
</form>`,
{ headers: { "content-type": "text/html" } },
);
},
async fetch(request) {
const url = new URL(request.url);
if (url.pathname.endsWith("/login") && request.method === "POST") {
const data = await request.formData();
const user = data.get("user");
const password = data.get("password");
const config = this.options.users.get(user);
if (config && config.password === password) {
return new Response(null, {
status: 302,
headers: {
location: this.options.basePath,
"set-cookie": `session=${user}; path=/; Secure; HttpOnly; SameSite=Strict`,
},
});
}
return new Response("Invalid credentials", { status: 401 });
}
return new Response("Not found", { status: 404 });
},
logout() {
return new Response(null, {
status: 302,
headers: {
location: this.options.basePath,
"set-cookie": "session=; path=/; Secure; HttpOnly; Max-Age=0",
},
});
},
},
);
Note
The session cookie in this example is not signed or encrypted, so it should not be used in production as-is. For a production setup, consider signing the cookie value or using a proper session library.