If you haven't already, I would recommend having a quick look at the Introduction & Sequence Diagram
Welcome to the 3-part series that helps you create a scalable production-ready authentication system using pure JWT & a middleware for your SvelteKit project
- Part 1: Setup & JWT Basics
- Part 2: Authentication Flows
- Part 3: Protecting Routes & Security
You are reading Part 1
Goal: Set up the project and core components for JWT authentication
Topics we'll cover
- Tech Stack & Prerequisites: SvelteKit, TypeScript, database (e.g., PostgreSQL), bcrypt, jsonwebtoken
- Setup: Create a SvelteKit project & install dependencies
- Database Schema: users and jwt_token_logs tables
- Database Interface: Functions like createUser, getUserByEmail, logJwtToken
- JWT Utilities: Implement generateToken, verifyToken, logToken, and authenticateRequest functions along with cookie module
Tech Stack & Prerequisites
Our implementation uses:
- SvelteKit
- TypeScript
- YOUR_DATABASE_CHOICE (PostgreSQL, MySQL, etc.)
- bcrypt (for password hashing)
- jsonwebtoken (for JWT handling)
Setup
First, let's create a new SvelteKit project and install the necessary dependencies:
# Create a new SvelteKit project
npm create svelte@latest my-auth-app
cd my-auth-app
# Install dependencies
npm install bcrypt jsonwebtoken
npm install --save-dev @types/bcrypt @types/jsonwebtoken
# Install database driver
npm install [YOUR_DATABASE_DRIVER]
If the above code doesn't work, don't worry!
You have two alternatives.
- Create a project from SvelteKit official documentation
- Ask ChatGPT how to create a Sveltekit project with a database & follow the steps
Database Schema
We'll need two tables:
-
users
- Stores user information -
jwt_token_logs
- Logs JWT issuance for audit purposes (not for validation)
Users Table
-- users.sql
CREATE TABLE USERS (
USER_ID VARCHAR(100) PRIMARY KEY,
EMAIL VARCHAR(255) NOT NULL UNIQUE,
PASSWORD VARCHAR(255) NOT NULL,
ROLE VARCHAR(50) NOT NULL DEFAULT 'user',
CREATED_AT DATETIME DEFAULT GETDATE(),
LAST_LOGIN DATETIME NULL
);
JWT Token Logs Table
-- jwt_token_logs.sql
CREATE TABLE JWT_TOKEN_LOGS (
LOG_ID UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(), -- Unique ID for the log entry
USER_ID VARCHAR(100) NOT NULL, -- User the token was issued to
TOKEN_VALUE TEXT NOT NULL, -- The JWT itself. Consider security implications of logging the full token.
ISSUED_AT DATETIME NOT NULL, -- Timestamp from within the JWT 'iat' claim
EXPIRES_AT DATETIME NOT NULL, -- Timestamp from within the JWT 'exp' claim
LOGGED_AT DATETIME DEFAULT GETDATE(), -- Timestamp when this log entry was created
ADDITIONAL_DATA TEXT -- Optional: Store context like IP address, user agent, etc.
);
Database Interface
Create a database interface to interact with our tables:
// src/database/db.ts
import crypto from "crypto";
import bcrypt from "bcrypt";
// [INSERT YOUR DATABASE CONNECTION SETUP CODE HERE]
...
export async function createUser(
email: string,
password: string,
role: string = "user"
): Promise<any> {
try {
const userId = crypto.randomUUID();
// Start a transaction to ensure atomicity
...
try {
// Step 1: Insert the user in database
...
// Step 2: Get the inserted user from database
...
// Commit the transaction
...
// Return user
return user;
} catch (error) {
// If any error occurs, roll back the transaction
await transaction.rollback();
throw error;
}
} catch (error) {
console.error("Error creating user:", error);
return null;
}
}
export async function getUserByEmail(email: string): Promise<any> {
try {
// Fetch the user by email from database
const result = await ...
// Return user
return result.recordset[0] || null;
} catch (error) {
console.error("Error fetching user:", error);
return null;
}
}
export async function validateUserCredentials(
email: string,
password: string
): Promise<any> {
try {
// Get user by email from database
const user = await getUserByEmail(email);
// If no user found with this email
if (!user) {
return null;
}
// Compare provided password with stored hash
const isPasswordValid = await bcrypt.compare(
password,
user.PASSWORD
);
if (!isPasswordValid) {
return null;
}
// Update last login time of the user in database
await updateLastLogin(user.USER_ID);
// Return user without password
const { PASSWORD, ...userWithoutPassword } = user;
return userWithoutPassword;
} catch (error) {
console.error("Error validating credentials:", error);
return null;
}
}
export async function updateLastLogin(
userId: string
): Promise<boolean> {
try {
// Update last login based on userId in database
...
return true;
} catch (error) {
console.error("Error updating last login:", error);
return false;
}
}
export async function logJwtToken(
userId: string,
tokenValue: string,
issuedAt: Date,
expiresAt: Date
): Promise<void> {
try {
// log JWT token in database
...
} catch (error) {
console.error("Error logging JWT token:", error);
}
}
JWT Utilities
Create a JWT utility module:
// src/lib/auth/jwt.ts
import jwt from "jsonwebtoken";
import type { Cookies } from "@sveltejs/kit";
import { dev } from "$app/environment";
import { logJwtToken } from "$lib/database/db";
import { getAuthToken } from "./cookies";
// Re-export cookie functions for backward compatibility
export { setAuthCookie, clearAuthCookies } from "./cookies";
// Types
export interface JwtPayload {
userId: string;
email: string;
role?: string;
iat?: number;
exp?: number;
}
interface TokenResponse {
success: boolean;
message?: string;
user?: Omit<JwtPayload, "iat" | "exp">;
error?: any;
}
// Configuration
const JWT_SECRET =
process.env.JWT_SECRET ||
"your-fallback-secret-key-change-in-production";
const ACCESS_TOKEN_EXPIRY = "1h"; // 1 hour
/**
* Generate a JWT token for a user
*/
export const generateToken = (
payload: Omit<JwtPayload, "iat" | "exp">
): string => {
return jwt.sign(payload, JWT_SECRET, {
expiresIn: ACCESS_TOKEN_EXPIRY,
});
};
/**
* Verify the validity of a JWT token
*/
export const verifyToken = (token: string): TokenResponse => {
try {
const decoded = jwt.verify(token, JWT_SECRET) as JwtPayload;
return {
success: true,
user: {
userId: decoded.userId,
email: decoded.email,
role: decoded.role,
},
};
} catch (error) {
return {
success: false,
message:
error instanceof Error ? error.message : "Invalid token",
error,
};
}
};
/**
* Log JWT token issuance to database
*/
export const logToken = async (
token: string,
userId: string
): Promise<void> => {
try {
// Decode token to get claims without verification
const decoded = jwt.decode(token) as JwtPayload;
const issuedAt = new Date((decoded.iat || 0) * 1000);
const expiresAt = new Date((decoded.exp || 0) * 1000);
// Use the database function to log the token
await logJwtToken(userId, token, issuedAt, expiresAt);
} catch (error) {
console.error("Error logging token:", error);
// Don't throw, as token logging failure shouldn't break authentication
}
};
/**
* Handle token-based authentication
* Returns user info if authenticated or null if not
*/
export const authenticateRequest = (
cookies: Cookies
): TokenResponse => {
const accessToken = getAuthToken(cookies);
if (!accessToken) {
return {
success: false,
message: "No authentication token found",
};
}
return verifyToken(accessToken);
};
Create a cookie module.
// src/lib/auth/cookies.ts
import type { Cookies } from "@sveltejs/kit";
import { dev } from "$app/environment";
// Configuration for authentication cookies
export const COOKIE_OPTIONS = {
path: "/",
httpOnly: true,
secure: !dev, // Only use secure in production
sameSite: "strict" as const,
maxAge: 60 * 60, // 1 hour in seconds
};
/**
* Set authentication cookie in the response
*/
export const setAuthCookie = (
cookies: Cookies,
accessToken: string
): void => {
cookies.set("access_token", accessToken, COOKIE_OPTIONS);
};
/**
* Clear authentication cookies
*/
export const clearAuthCookies = (cookies: Cookies): void => {
cookies.delete("access_token", { path: "/" });
cookies.delete("user", { path: "/" }); // Clear the existing user cookie as well
};
/**
* Get authentication token from cookies
*/
export const getAuthToken = (
cookies: Cookies
): string | undefined => {
return cookies.get("access_token");
};
Next → Part 2: Authentication Flows
Previous → Introduction & Sequence Diagram