Uses SvelteKit, Supabase, and SSR Auth.
authenticated
layout group.nickname
user_metadata on the /self
page./self
page./self
page - if needed, when playing around with the demo.All actions happen server-side.
git clone https://github.com/j4w8n/sveltekit-supabase-ssr.git
cd sveltekit-supabase-ssr
npm install
Environment variables.
Rename the .env.example
file to .env.local
in your project's root directory and assign values. They can be found in your Supabase project's dashboard at Project Settings > Data API. !!! Never expose your JWT_SECRET
on the client side !!!
PUBLIC_SUPABASE_ANON_KEY=<your-project-anon-key>
PUBLIC_SUPABASE_URL=https://<your-project-id>.supabase.co
JWT_SECRET=<your-project-jwt-secret>
SUPABASE_SERVICE_ROLE_KEY=<your-project-service-role-key>
If using the signup, magiclink, or reset password features, change their email template anchor links per the below. In your Supabase project's dashboard, go to Authentication > Emails.
All need this: href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email"
, which should replace the {{ .ConfirmationURL }}
, and then there are some additions for magic link and reset:
&next=/app
at the end of the above href.&next=/self
at the end of the above href.Site URL and Redirect URLs. In your Supabase project's dashboard, go to Authentication > URL Configuration, and change them to reflect the below.
http://localhost:5173
http://localhost:5173/auth/confirm
http://localhost:5173/auth/callback
User and provider settings. In your Supabase project's dashbord, go to Authentication > Sign In / Up
npm run dev
Open a browser to http://localhost:5173
Within the (authenticated)
layout group, we have a +page.server.ts
file for each route. This ensures that even during "client-side navigation" the hooks.server.ts
file is run so that we can verify there's still a session before rendering the page. I put that in double-quotes because this process essentially disables client-side navigation for these pages.
We check for and fully validate the session by calling event.locals.getSession()
. Inside that function, we call getClaims
to verify the access_token
, aka JWT, and use it's decoded contents to help create a validated session for use on the server-side. This validation is important because sessions are stored in a cookie sent from a client. The client could be an attacker with just enough information to bypass checks in a simple supabase.auth.getSession()
call, and possibly render data for a victim user. See this discussion for details.
!!! Just verifying the JWT does not validate other information within getSession's session.user
object; this is a big reason why we do the "full validation" by replacing its contents using info from the verified and decoded JWT. See discussion link above. !!!