DEV Community

Mukesh
Mukesh

Posted on

Part 1: Setup & JWT Basics

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


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] 
Enter fullscreen mode Exit fullscreen mode

If the above code doesn't work, don't worry!

You have two alternatives.

Database Schema

We'll need two tables:

  1. users - Stores user information
  2. 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 ); 
Enter fullscreen mode Exit fullscreen mode

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. ); 
Enter fullscreen mode Exit fullscreen mode

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); } } 
Enter fullscreen mode Exit fullscreen mode

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); }; 
Enter fullscreen mode Exit fullscreen mode

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"); }; 
Enter fullscreen mode Exit fullscreen mode

Next → Part 2: Authentication Flows

Previous → Introduction & Sequence Diagram

Top comments (0)