Roller og innlogging m/Supabase + HTML + JavaScript
I denne guiden lager vi et enkelt innloggingssystem i ren HTML/CSS/JavaScript som snakker med Supabase.
✅ Du setter inn din egen e-post som admin i SQL-koden.
Alle andre som registrerer seg blir automatisk user.
Trinn 1 — Supabase: prosjektnøkler, tabeller & regler
- 1.1Opprett (eller åpne) et prosjekt på supabase.com.
- 1.2Finn Project URL og anon
public key i Project Settings → "API Keys" og Project Settings → "Data API". Disse
bruker
vi i
script.js. - 1.3Åpne SQL Editor i
supabase og kjør skriptet under
for å opprette
profiles, slå på RLS (Row-level security: for å skjule passord) - 1.4Sett din e-post som admin: Bytt ut teksten skriv inn din e-post her med din faktiske e-post før du kjører.
-- Tabell for brukerprofiler (knyttet til auth.users)
create table if not exists profiles (
id uuid primary key references auth.users on delete cascade,
role text not null default 'user', -- standardrolle er "user"
created_at timestamptz default now()
);
-- Slå på RLS (rad-nivå-sikkerhet)
alter table profiles enable row level security;
-- Policy: Bruker kan bare lese sin egen profil
drop policy if exists "read-own-profile" on profiles;
create policy "read-own-profile"
on profiles for select using (id = auth.uid());
-- Trigger: opprett profil og gi admin-rolle til DIN e-post
create or replace function public.handle_new_user()
returns trigger as $$
begin
insert into public.profiles (id, role)
values (
new.id,
case when lower(new.email) = lower('skriv inn din e-post her') then 'admin' else 'user' end
);
return new;
end;
$$ language plpgsql security definer;
drop trigger if exists on_auth_user_created on auth.users;
create trigger on_auth_user_created
after insert on auth.users
for each row execute function public.handle_new_user();
Trinn 2 — index.html: navbar, hero og innloggingspanel
Lag index.html. Vi beholder enkel, ryddig navigasjon og legger til en
«Logg inn»-knapp + sidepanel (for innlogging/opprett bruker).
<!doctype html>
<html lang="no">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Innlogging med Supabase</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="style.css" rel="stylesheet">
</head>
<body>
<!-- NAVBAR -->
<nav class="navbar navbar-expand-md navbar-dark bg-black border-bottom" aria-label="Hovednavigasjon">
<div class="container d-flex justify-content-between align-items-center">
<a class="navbar-brand fw-semibold text-white" href="index.html">Læringsportalen</a>
<div>
<button id="authOpenBtn" class="btn btn-sm btn-outline-light">Logg inn</button>
<span id="authUserBadge" class="ms-2 small text-white-50"></span>
</div>
</div>
</nav>
<!-- HERO -->
<header class="py-5 bg-light border-bottom">
<div class="container">
<h1 class="h3 fw-bold">Supabase + HTML + JS: enkel innlogging</h1>
<p class="text-muted mb-0">Innlogging, registrering og roller (admin/user) uten rammeverk.</p>
</div>
</header>
<main class="container py-4">
<h2 class="h5">Velkommen</h2>
<p class="text-muted">Bruk knappen <em>Logg inn</em> oppe til høyre for å teste løsningen.</p>
</main>
<!-- INNLOGGINGSPANEL (skjult) -->
<aside id="authPanel" class="auth-panel" aria-hidden="true">
<header class="p-3 border-bottom d-flex justify-content-between align-items-center">
<strong>Min profil</strong>
<button id="authCloseBtn" class="btn btn-sm btn-outline-secondary">Lukk</button>
</header>
<div class="p-3">
<!-- Ikke innlogget -->
<div id="authLoggedOut">
<div class="d-flex gap-2 mb-3">
<button class="btn btn-sm btn-outline-dark active" data-tab="login">Logg inn</button>
<button class="btn btn-sm btn-outline-dark" data-tab="signup">Opprett profil</button>
</div>
<!-- Logg inn -->
<form id="loginForm" data-panel="login">
<input class="form-control mb-2" type="email" id="loginEmail" placeholder="E-post" required />
<input class="form-control mb-3" type="password" id="loginPass" placeholder="Passord" required />
<button class="btn btn-dark w-100 mb-2" type="submit">Logg inn</button>
<div id="loginMsg" class="text-muted small"></div>
</form>
<!-- Opprett bruker -->
<form id="signupForm" data-panel="signup" style="display:none;">
<input class="form-control mb-2" type="text" id="signupName" placeholder="Brukernavn (valgfritt)" />
<input class="form-control mb-2" type="email" id="signupEmail" placeholder="E-post" required />
<input class="form-control mb-3" type="password" id="signupPass" placeholder="Passord (min. 6 tegn)" required />
<button class="btn btn-dark w-100 mb-2" type="submit">Opprett profil</button>
<div class="text-muted small">Admin settes automatisk til e-posten du oppga i SQL-koden.</div>
<div id="signupMsg" class="text-muted small mt-2"></div>
</form>
</div>
<!-- Innlogget -->
<div id="authLoggedIn" style="display:none;">
<p class="small text-muted mb-1" id="whoami"></p>
<div id="roleBadge" class="mb-2 small"></div>
<button id="logoutBtn" class="btn btn-outline-dark w-100">Logg ut</button>
</div>
</div>
</aside>
<script type="module" src="script.js"></script>
</body>
</html>
Hvorfor panel? Elevene holder fokus på siden og slipper å navigere bort. Panelet fungerer på mobil og desktop.
Trinn 3 — style.css: enkel, videreførbar stil
Lag style.css (du kan starte med lite – bygg videre senere):
/* Base */
html, body { height:100%; }
body { font-family: system-ui,-apple-system,"Segoe UI",Roboto,Arial,sans-serif; background:#f7f9fc; color:#111; margin:0; }
/* Auth-panel */
.auth-panel{
position:fixed; top:0; right:0; height:100vh; width:340px; max-width:90vw;
background:#fff; border-left:1px solid #e9ecef; box-shadow:-10px 0 24px rgba(0,0,0,.08);
transform:translateX(100%); transition:transform .25s ease; z-index:1200;
}
.auth-panel.open{ transform:translateX(0); }
/* Små hjelpere (optional) */
.card-hover{ transition:box-shadow .2s ease }
.card-hover:hover{ box-shadow:0 .5rem 1rem rgba(0,0,0,.08) }
Har du allerede en stilfil? Legg bare til panel-klassene.
Trinn 4 — script.js: koble HTML ←→ Supabase
Lag script.js og bytt ut dine egne API-nøkler (rød markering
under).
Finn nøkler i Project Settings → API.
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
/** 1) Koble til Supabase (BYTT til dine nøkler) */
const supabase = createClient(
"https://DIN-PROSJEKT-URL.supabase.co", // ← Project URL
"DIN-ANON-PUBLIC-KEY" // ← anon public key
);
/** 2) Finn elementer */
const panel = document.getElementById('authPanel');
const openBtn = document.getElementById('authOpenBtn');
const closeBtn = document.getElementById('authCloseBtn');
const userBadge = document.getElementById('authUserBadge');
const loggedOut = document.getElementById('authLoggedOut');
const loggedIn = document.getElementById('authLoggedIn');
const whoami = document.getElementById('whoami');
const roleBadge = document.getElementById('roleBadge');
const tabButtons = document.querySelectorAll('[data-tab]');
const loginForm = document.getElementById('loginForm');
const signupForm = document.getElementById('signupForm');
const loginMsg = document.getElementById('loginMsg');
const signupMsg = document.getElementById('signupMsg');
/** 3) Åpne/lukk panel */
openBtn.addEventListener('click', ()=> panel.classList.add('open'));
closeBtn.addEventListener('click', ()=> panel.classList.remove('open'));
/** 4) Bytt mellom "Logg inn" og "Opprett profil" */
tabButtons.forEach(btn=>{
btn.addEventListener('click', ()=>{
tabButtons.forEach(b=>b.classList.remove('active'));
btn.classList.add('active');
const tab = btn.dataset.tab;
loginForm.style.display = tab === 'login' ? '' : 'none';
signupForm.style.display = tab === 'signup' ? '' : 'none';
});
});
/** 5) Oppdater UI (bruker + rolle) */
async function refreshAuthUI() {
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
loggedOut.style.display = '';
loggedIn.style.display = 'none';
userBadge.textContent = '';
return;
}
// Les rolle fra "profiles" (RLS: bruker kan bare lese sin egen rad)
const { data: profile } = await supabase
.from('profiles')
.select('role')
.eq('id', user.id)
.maybeSingle();
const role = profile?.role ?? 'user';
loggedOut.style.display = 'none';
loggedIn.style.display = '';
whoami.textContent = \`Innlogget som \${user.email}\`;
roleBadge.textContent = \`Rolle: \${role}\`;
userBadge.textContent = (role === 'admin') ? 'Admin' : 'Innlogget';
}
/** 6) Logg inn */
loginForm.addEventListener('submit', async (e)=>{
e.preventDefault();
loginMsg.textContent = 'Logger inn...';
const email = document.getElementById('loginEmail').value.trim();
const pass = document.getElementById('loginPass').value;
const { error } = await supabase.auth.signInWithPassword({ email, password: pass });
loginMsg.textContent = error ? 'Feil: ' + error.message : 'Innlogget ✅';
await refreshAuthUI();
if (!error) setTimeout(()=>panel.classList.remove('open'), 400);
});
/** 7) Opprett bruker */
signupForm.addEventListener('submit', async (e)=>{
e.preventDefault();
signupMsg.textContent = 'Oppretter...';
const name = document.getElementById('signupName').value.trim();
const email = document.getElementById('signupEmail').value.trim().toLowerCase();
const pass = document.getElementById('signupPass').value;
const { error } = await supabase.auth.signUp({
email, password: pass, options: { data: { display_name: name } }
});
signupMsg.textContent = error
? 'Feil: ' + error.message
: 'Konto opprettet ✅ Du kan logge inn nå.';
});
/** 8) Logg ut */
document.getElementById('logoutBtn').addEventListener('click', async ()=>{
await supabase.auth.signOut();
await refreshAuthUI();
});
/** 9) Hold UI i sync */
supabase.auth.onAuthStateChange(() => refreshAuthUI());
refreshAuthUI();
supabase.auth.*.
Rollen leses fra tabellen profiles (RLS sikrer at du bare kan lese din egen rad).
Triggeren fra Trinn 1 gir admin til din e-post, og user
til alle andre.
Test & feilsøking
- AÅpne
index.htmlmed Live Server (VS Code) eller last opp til GitHub Pages. - BKlikk Logg inn → Opprett profil → registrer e-post og passord → logg inn.
- CSjekk i Supabase → Table Editor → profiles
at rollen er
adminfor din e-post, ellersuser.
- “permission denied for table profiles” → Kjør SQL-skriptet (Trinn 1) på nytt, spesielt policyen.
- “Failed to fetch” → Sjekk at Project URL og anon key er korrekte (se rød
markering i
script.js). - Ingenting skjer → Åpne nettleser-konsollen (F12) og se feilmelding. Sjekk
at
script.jslastes riktig.