Part 1: Setup & JWT Basics

Mukesh JaiswalMukesh Jaiswal
6 min read

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]

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
);

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

0
Subscribe to my newsletter

Read articles from Mukesh Jaiswal directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Mukesh Jaiswal
Mukesh Jaiswal

Driving business growth with AI Automation (as Business Automation Partner) | Helping startups build (as Software Developer)