Updated for Svelte 5 and SvelteKit 2.8.1
This is an example of how to register, authenticate, and update users and limit their access to areas of the website by role (admin, teacher, student). It includes profile management and password resets via SendGrid.
It's a Single Page App (SPA) built with SvelteKit and a PostgreSQL database back-end. Code is TypeScript and the website is styled using Bootstrap. PostgreSQL functions handle password hashing and UUID generation for the session ID. Unlike most authentication examples, this SPA does not use callbacks that redirect back to the site (causing the website to be reloaded with a visual flash).
The project includes a Content Security Policy (CSP) in svelte.config.js.
The website supports two types of authentication:
- Local accounts via username (email) and password
- The login form (/src/routes/login/+page.svelte) sends the login info as JSON to endpoint /auth/login
- The endpoint passes the JSON to PostgreSQL function authenticate(json) which hashes the password and compares it to the stored hashed password in the users table. The function returns JSON containing a session ID (v4 UUID) and user object (sans password).
- The endpoint sends this session ID as an httpOnly SameSite cookie and the user object in the body of the response.
- The client stores the user object in the loginSession store.
- Further requests to the server include the session cookie. The hooks.ts handle() method extracts the session cookie, looks up the user and attaches it to RequestEvent.locals so server-side code can check locals.user.role to see if the request is authorized and return an HTTP 401 status if not.
- Sign in with Google
- Sign in with Google is initialized in /src/routes/+layout.svelte.
- Google One Tap prompt is displayed on the initially loaded page unless Intelligent Tracking Prevention is enabled in the browser.
- Sign in with Google button is on the login page (/src/routes/login/+page.svelte) and register page (/src/routes/register/+page.svelte).
- Clicking either button opens a new window asking the user to authorize this website. If the user OKs it, a JSON Web Token (JWT) is sent to a callback function.
- The callback function (in /src/lib/auth.ts) sends the JWT to an endpoint on this server /auth/google.
- The endpoint decodes and validates the user information then calls the PostgreSQL function start_gmail_user_session to upsert the user to the database returing a session id in an httpOnly SameSite cookie and user in the body of the response.
- The client stores the user object in the loginSession store.
- Further requests to the server work identically to local accounts above.
There is some overhead to checking the user session in a database each time versus using a JWT; however, validating each request avoids problems discussed in this article and this one. For a high-volume website, I would use Redis or the equivalent.
The forgot password / password reset functionality uses a JWT and SendGrid to send the email. You would need to have a SendGrid account and set two environmental variables. Email sending is in /src/routes/auth/forgot.ts. This code could easily be replaced by nodemailer or something similar. Note: I have no affliation with SendGrid (used their API in another project).
- PostgreSQL 14.10 or higher
- Node.js 18.19.1 or higher
- Google API client
- Twilio SendGrid account (only used for emailing password reset link - the sample can run without it but forgot password will not work)
Here are the steps:
- Get the project and setup the database
# Clone the repo to your current directory
git clone https://github.com/nstuyvesant/sveltekit-auth-example.git
# Install the dependencies
cd /sveltekit-auth-example
yarn install
# Create PostgreSQL database (only works if you installed PostgreSQL)
psql -d postgres -f db_create.sql
-
Create a Google API client ID per these instructions. Make sure you include
http://localhost:3000
,http://localhost
in the Authorized JavaScript origins andhttp://localhost:3000/auth/google/callback
in the Authorized redirect URIs for your Client ID for Web application. ** Do not access the site using http://127.0.0.1:3000 ** - usehttp://localhost:3000
or it will not work. -
Create a free Twilio SendGrid account and generate an API Key following this documentation and add a sender as documented here.
-
Create an .env file at the top level of the project with the following values (substituting your own id and PostgreSQL username and password):
DATABASE_URL=postgres://user:password@localhost:5432/auth
DOMAIN=http://localhost:3000
JWT_SECRET=replace_with_your_own
SENDGRID_KEY=replace_with_your_own
SENDGRID_SENDER=replace_with_your_own
PUBLIC_GOOGLE_CLIENT_ID=replace_with_your_own
# Start the server and open the app in a new browser tab
yarn dev -- --open
The db_create.sql script adds three users to the database with obvious roles:
- [email protected] password admin123
- [email protected] password teacher123
- [email protected] password student123