📚Academy
likeone
online

Auth & Tokens

Authentication is how your app knows who is talking to it. JWTs (JSON Web Tokens) are the standard. Understanding them is the difference between a secure app and a data breach waiting to happen.

Why Authentication Matters

Without authentication, every request to your app is anonymous. Anyone can read anyone's data. Anyone can modify anyone's records. Authentication proves identity — it answers "who is making this request?" before your app does anything.

Supabase handles the hard parts (password hashing, session management, email verification) so you never write security-critical code yourself. You get a battle-tested auth system with one import.

🔒
JWT Standard
Industry standard for stateless authentication — no server-side sessions needed
🛡️
RLS + JWT
Database-level security using token claims — even buggy code cannot bypass it
Zero Custom Code
Supabase Auth handles signup, login, password reset, email verification

JWT Anatomy: Three Parts, One Token

A JWT has three parts separated by dots. Each part is base64url-encoded. Together they form a self-contained credential that proves who the user is without hitting a database on every request.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZW1haWwiOiJidWlsZGVyQGV4YW1wbGUuY29tIiwicm9sZSI6ImF1dGhlbnRpY2F0ZWQiLCJpYXQiOjE3MTExNTIwMDAsImV4cCI6MTcxMTE1NTYwMH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Header (Red) — Algorithm + Type
{ "alg": "HS256", // HMAC-SHA256 signing algorithm
  "typ": "JWT" } // token type

Tells the server which algorithm was used to create the signature. HS256 (HMAC with SHA-256) is the most common for Supabase.

Payload (Purple) — Claims (Your Data)
{ "sub": "1234567890", // subject (user ID)
  "email": "builder@example.com", // custom claim
  "role": "authenticated", // Supabase role
  "iat": 1711152000, // issued at (Unix timestamp)
  "exp": 1711155600 } // expires in 1 hour

The actual data. sub is the user ID — Supabase's auth.uid() extracts this for RLS. exp is when the token expires (default: 1 hour). The payload is NOT encrypted — anyone can decode it. It is only signed.

Signature (Green) — Verification
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret // your JWT secret (server-side only)
)

The server uses this to verify the token was not tampered with. If anyone changes the payload (e.g., swapping their user ID for another's), the signature will not match, and the server rejects the request.

Key insight: JWTs are signed, not encrypted. Anyone can decode the payload (it is just base64). The signature only prevents tampering — it proves the data has not been modified since the server issued it. Never put secrets in JWT payloads.

The Auth Flow: Signup to Database Query

Here is exactly what happens from the moment a user signs up to the moment they read their own data — and how JWT + RLS work together at each step.

1. User signs up — email + password sent to Supabase Auth via supabase.auth.signUp()
2. Email verification — Supabase sends a magic link or OTP to confirm the address
3. JWT issued — after verification, Supabase returns an access token (1hr) + refresh token (long-lived)
4. Authenticated requests — the JWT travels in the Authorization: Bearer header with every API call
5. RLS enforced — Postgres extracts auth.uid() from the JWT and scopes all queries to that user's rows

Supabase Auth in Code

The Supabase client handles token management automatically — storing tokens, refreshing expired ones, and attaching them to requests. You rarely need to touch JWTs directly.

JavaScript — Frontend auth with Supabase
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
)

// Sign up a new user
const { data, error } = await supabase.auth.signUp({
  email: 'builder@example.com',
  password: 'secure-password-123'
})

// Sign in an existing user
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'builder@example.com',
  password: 'secure-password-123'
})

// Get the current session (includes the JWT)
const { data: { session } } = await supabase.auth.getSession()
console.log(session.access_token) // This is the JWT!

// All subsequent queries automatically use the JWT
// RLS scopes results to this user's rows
const { data } = await supabase
  .from('user_data')
  .select('*')  // returns only this user's rows
🔒

This lesson is for Pro members

Unlock all 520+ lessons across 52 courses with Academy Pro.

Already a member? Sign in to access your lessons.

Academy
Built with soul — likeone.ai