Express.js
js
import express from "express";
import crypto from "node:crypto";
import cookieParser from "cookie-parser";
const app = express();
app.use(cookieParser(process.env.COOKIE_SECRET));
const LOGI = process.env.LOGI_API_URL;
const CLIENT_ID = process.env.LOGI_CLIENT_ID;
const CLIENT_SECRET = process.env.LOGI_CLIENT_SECRET;
const REDIRECT = process.env.LOGI_REDIRECT_URI;
function b64url(buf) {
return Buffer.from(buf).toString("base64url");
}
app.get("/auth/login", (req, res) => {
const verifier = b64url(crypto.randomBytes(32));
const challenge = b64url(crypto.createHash("sha256").update(verifier).digest());
const state = b64url(crypto.randomBytes(16));
res.cookie("logi_pkce", verifier, { httpOnly: true, secure: true, sameSite: "lax", maxAge: 600_000 });
res.cookie("logi_state", state, { httpOnly: true, secure: true, sameSite: "lax", maxAge: 600_000 });
const url = new URL(`${LOGI}/oauth/authorize`);
url.searchParams.set("client_id", CLIENT_ID);
url.searchParams.set("redirect_uri", REDIRECT);
url.searchParams.set("response_type", "code");
url.searchParams.set("scope", "profile email");
url.searchParams.set("state", state);
url.searchParams.set("code_challenge", challenge);
url.searchParams.set("code_challenge_method", "S256");
res.redirect(url.toString());
});
app.get("/auth/callback", async (req, res) => {
if (req.query.state !== req.cookies.logi_state) return res.status(400).send("state mismatch");
const tokens = await fetch(`${LOGI}/oauth/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code: req.query.code,
redirect_uri: REDIRECT,
code_verifier: req.cookies.logi_pkce,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
}),
}).then(r => r.json());
res.cookie("logi_rt", tokens.refresh_token, { httpOnly: true, secure: true, sameSite: "strict" });
res.clearCookie("logi_pkce");
res.clearCookie("logi_state");
res.redirect("/");
});