Part 3: Protecting Routes and Security

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 3 Goal: Secure the app with middleware and discuss best practices for a production-ready system. Topics we'll cover Server Hooks (Middleware): Use SvelteKit hooks to verify JWT and secure routes. Security Considerations: Token storage (HTTP-only cookies), CSRF protection, token expiration, HTTPS, etc. Conclusion & Next Steps: Recap and suggestions like refresh tokens or role-based access. SvelteKit Server Hooks (Middleware) Now we'll implement the authentication middleware in SvelteKit's server hooks: // src/hooks.server.ts import { authenticateRequest } from "$lib/auth/jwt"; import { clearAuthCookies } from "$lib/auth/cookies"; import type { Handle } from "@sveltejs/kit"; import type { JwtPayload } from "$lib/auth/jwt"; // Extend the Locals interface to include our user property declare global { namespace App { interface Locals { user?: Omit; } } } export const handle: Handle = async ({ event, resolve }) => { const { cookies, url } = event; // Public routes that don't require authentication const publicRoutes = [ "/auth/sign-in", "/auth/sign-up", "/auth/forgot-password", "/auth/reset-password", "/auth/logout", ]; // Check if the current route is public const isPublicRoute = publicRoutes.some( (route) => url.pathname === route || url.pathname.startsWith("/api/public/") ); // First, verify the authentication status for all routes const authResult = authenticateRequest(cookies); if (authResult.success && authResult.user) { // User is authenticated event.locals.user = authResult.user; // Maintain backward compatibility with existing code if (!cookies.get("user")) { cookies.set("user", JSON.stringify(authResult.user), { path: "/", httpOnly: true, }); } // If user is authenticated and trying to access public routes like sign-in // redirect them to the dashboard instead if (isPublicRoute && url.pathname !== "/auth/logout") { return Response.redirect(new URL("/dashboard", url)); } } else { // User is not authenticated if (!isPublicRoute) { // Non-public route requires authentication, redirect to login clearAuthCookies(cookies); return Response.redirect(new URL("/auth/sign-in", url)); } } return resolve(event); }; Security Considerations When implementing JWT authentication, consider the following security aspects: Token Storage: Store tokens in HTTP-only cookies to prevent XSS attacks CSRF Protection: Implement CSRF tokens for form submissions Token Expiration: Use short-lived tokens to minimize damage from token theft Secure Connection: Always use HTTPS in production Environment Variables: Store your JWT secret in environment variables, not in code Conclusion We've built a complete authentication system using SvelteKit, TypeScript, and "pure JWT" authentication. This approach gives us the benefits of stateless authentication while still maintaining proper user management and security audit capabilities. The key advantage of this implementation is that we don't need to query the database for each authenticated request, making our application more scalable. Instead, we rely on the cryptographic integrity of the JWT itself to validate the user's identity. Next Steps To further enhance this system, you might consider: Implementing refresh tokens for longer sessions Adding email verification for signup Supporting password reset functionality Implementing role-based access control Adding two-factor authentication I hope this tutorial helps you build secure and scalable authentication systems with SvelteKit! Previous → Part 2: Authentication Flows

May 6, 2025 - 12:12
 0
Part 3: Protecting Routes and Security

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 3

Goal: Secure the app with middleware and discuss best practices for a production-ready system.

Topics we'll cover

  • Server Hooks (Middleware): Use SvelteKit hooks to verify JWT and secure routes.
  • Security Considerations: Token storage (HTTP-only cookies), CSRF protection, token expiration, HTTPS, etc.
  • Conclusion & Next Steps: Recap and suggestions like refresh tokens or role-based access.

SvelteKit Server Hooks (Middleware)

Now we'll implement the authentication middleware in SvelteKit's server hooks:

// src/hooks.server.ts
import { authenticateRequest } from "$lib/auth/jwt";
import { clearAuthCookies } from "$lib/auth/cookies";
import type { Handle } from "@sveltejs/kit";
import type { JwtPayload } from "$lib/auth/jwt";

// Extend the Locals interface to include our user property
declare global {
  namespace App {
    interface Locals {
      user?: Omit<JwtPayload, "iat" | "exp">;
    }
  }
}

export const handle: Handle = async ({ event, resolve }) => {
  const { cookies, url } = event;

  // Public routes that don't require authentication
  const publicRoutes = [
    "/auth/sign-in",
    "/auth/sign-up",
    "/auth/forgot-password",
    "/auth/reset-password",
    "/auth/logout",
  ];

  // Check if the current route is public
  const isPublicRoute = publicRoutes.some(
    (route) =>
      url.pathname === route ||
      url.pathname.startsWith("/api/public/")
  );

  // First, verify the authentication status for all routes
  const authResult = authenticateRequest(cookies);

  if (authResult.success && authResult.user) {
    // User is authenticated
    event.locals.user = authResult.user;

    // Maintain backward compatibility with existing code
    if (!cookies.get("user")) {
      cookies.set("user", JSON.stringify(authResult.user), {
        path: "/",
        httpOnly: true,
      });
    }

    // If user is authenticated and trying to access public routes like sign-in
    // redirect them to the dashboard instead
    if (isPublicRoute && url.pathname !== "/auth/logout") {
      return Response.redirect(new URL("/dashboard", url));
    }
  } else {
    // User is not authenticated
    if (!isPublicRoute) {
      // Non-public route requires authentication, redirect to login
      clearAuthCookies(cookies);
      return Response.redirect(new URL("/auth/sign-in", url));
    }
  }

  return resolve(event);
};

Security Considerations

When implementing JWT authentication, consider the following security aspects:

  1. Token Storage: Store tokens in HTTP-only cookies to prevent XSS attacks
  2. CSRF Protection: Implement CSRF tokens for form submissions
  3. Token Expiration: Use short-lived tokens to minimize damage from token theft
  4. Secure Connection: Always use HTTPS in production
  5. Environment Variables: Store your JWT secret in environment variables, not in code

Conclusion

We've built a complete authentication system using SvelteKit, TypeScript, and "pure JWT" authentication. This approach gives us the benefits of stateless authentication while still maintaining proper user management and security audit capabilities.

The key advantage of this implementation is that we don't need to query the database for each authenticated request, making our application more scalable. Instead, we rely on the cryptographic integrity of the JWT itself to validate the user's identity.

Next Steps

To further enhance this system, you might consider:

  1. Implementing refresh tokens for longer sessions
  2. Adding email verification for signup
  3. Supporting password reset functionality
  4. Implementing role-based access control
  5. Adding two-factor authentication

I hope this tutorial helps you build secure and scalable authentication systems with SvelteKit!

Previous → Part 2: Authentication Flows