Part 1: Setup & JWT Basics
Mukesh

Mukesh @jais_mukesh

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

Location:
Berlin, Germany
Joined:
Jun 17, 2018

Part 1: Setup & JWT Basics

Publish Date: May 6
1 0

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

Comments 0 total

    Add comment