Roller og innlogging m/Supabase 🔐
Lær hvordan du lager et profesjonelt innloggingssystem med automatiske roller (admin/user).
Sentrale konsepter
Autentisering
Supabase Auth
Håndterer brukere, passord og sikker pålogging.
Automatisering
SQL Triggere
En funksjon i databasen som kjører "bak kulissene" ved registrering.
Tilgangsstyring
Roller
Skiller mellom vanlige brukere og administratorer.
Trinn 1 — Supabase: Tabeller & SQL-trigger
- 1.1Opprett prosjekt på supabase.com.
- 1.2Finn Project URL og anon key under Settings -> API.
- 1.3Viktig: Bytt ut e-posten i koden under før du kjører den i SQL Editor.
-- 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: Struktur
Lag index.html. Denne inneholder selve siden og det skjulte panelet.
index.html, style.css og script.js skal ligge i samme mappe.
<!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-dark 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">learn-it.no</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">
<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>
<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>
<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 id="signupMsg" class="text-muted small mt-2"></div>
</form>
</div>
<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>
Trinn 3 — style.css: Design
/* Base */
html, body { height:100%; }
body { font-family: system-ui, 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); }
.card-hover{ transition:box-shadow .2s ease }
.card-hover:hover{ box-shadow:0 .5rem 1rem rgba(0,0,0,.08) }
Trinn 4 — script.js: Logikken
Husk: Bytt ut URL og API-nøkkel med dine egne.
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",
"DIN-ANON-PUBLIC-KEY"
);
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');
openBtn.addEventListener('click', ()=> panel.classList.add('open'));
closeBtn.addEventListener('click', ()=> panel.classList.remove('open'));
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';
});
});
async function refreshAuthUI() {
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
loggedOut.style.display = '';
loggedIn.style.display = 'none';
userBadge.textContent = '';
return;
}
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';
}
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);
});
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 ✅ Sjekk e-posten din for bekreftelse!.';
});
document.getElementById('logoutBtn').addEventListener('click', async ()=>{
await supabase.auth.signOut();
await refreshAuthUI();
});
supabase.auth.onAuthStateChange(() => refreshAuthUI());
refreshAuthUI();
Test & feilsøking
- "permission denied": Husk å kjøre SQL-scriptet i Trinn 1.
- "Failed to fetch": Sjekk URL og API-nøkkel i script.js.
- Finner ikke profil: Sjekk Table Editor i Supabase for å se om raden ble laget.