logo cosasdedevs
Autenticación en Svelte 5 y SvelteKit: Crea un Login y Gestión de Sesión con Lucia-Auth

Autenticación en Svelte 5 y SvelteKit: Crea un Login y Gestión de Sesión con Lucia-Auth



My Profile
Ene 14, 2025

En este tutorial vamos a ver como crear una página de login en SvelteKit utilizando Lucia-Auth, una biblioteca de autenticación para gestionar sesiones de forma segura y sencilla. 

Configuraremos un sistema desde cero, cubriendo todo el flujo: desde la validación de credenciales hasta la generación y manejo de la sesión del usuario.

Antes de empezar 🛑 y como siempre, si te has perdido alguno de los tutoriales de esta serie, aquí te dejo todos los tutoriales escritos hasta ahora:

¿Qué es Lucia-Auth?

Lucia-Auth es un recurso para aprender a implementar un sistema de autenticación desde 0 para JavaScript y TypeScript. La documentación de Svelte nos recomienda usar esta opción para implementar este sistema y tenemos dos opciones de hacerlo, mediante un comando y hacerlo manualmente siguiendo su documentación. Yo lo he hecho de la segunda forma, ya que tenemos avanzado el proyecto y podría dar problemas, pero de esta forma verás que vale la pena porque lo vamos a entender todo mucho mejor.

Instalaciones necesarias

Como primer paso vamos a instalar la librería de oslojs que la utilizaremos para generar el token de sesión y el id de sesión. Para instalarla lanzaremos el siguiente comando:

npm i @oslojs/encoding @oslojs/crypto

Crear sistema de autenticación

Lo siguiente que haremos será crear el archivo src\lib\server\db\auth.ts que se encargará de toda la gestión de los tokens, guardado de la sesión en la base de datos y de las cookies de sesión. Este archivo tendrá el siguiente contenido:

import { eq } from 'drizzle-orm';
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding';
import { sha256 } from '@oslojs/crypto/sha2';
import { db } from './db';
import { sessionsTable, usersTable, type SessionEntity, type UserEntity } from './schema';
import type { RequestEvent } from '@sveltejs/kit';
const DAY_IN_MS = 1000 * 60 * 60 * 24;
export const sessionCookieName = 'session';
export function generateSessionToken(): string {
    const bytes = new Uint8Array(20);
    crypto.getRandomValues(bytes);
    const token = encodeBase32LowerCaseNoPadding(bytes);
    return token;
}
export async function createSession(token: string, userId: number): Promise<SessionEntity> {
    const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
    const session: SessionEntity = {
        id: sessionId,
        userId,
        expiresAt: new Date(Date.now() + DAY_IN_MS * 30)
    };
    await db.insert(sessionsTable).values(session);
    return session;
}
export async function validateSessionToken(token?: string): Promise<SessionValidationResult> {
    if (!token) {
        return { session: null, user: null };
    }
    const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
    const result = await db
        .select({ user: usersTable, session: sessionsTable })
        .from(sessionsTable)
        .innerJoin(usersTable, eq(sessionsTable.userId, usersTable.id))
        .where(eq(sessionsTable.id, sessionId));
    if (result.length < 1) {
        return { session: null, user: null };
    }
    const { user, session } = result[0];
    if (Date.now() >= session.expiresAt.getTime()) {
        await db.delete(sessionsTable).where(eq(sessionsTable.id, session.id));
        return { session: null, user: null };
    }
    if (Date.now() >= session.expiresAt.getTime() - DAY_IN_MS * 15) {
        session.expiresAt = new Date(Date.now() + DAY_IN_MS * 30);
        await db
            .update(sessionsTable)
            .set({
                expiresAt: session.expiresAt
            })
            .where(eq(sessionsTable.id, session.id));
    }
    return { session, user };
}
export async function invalidateSession(sessionId: string): Promise<void> {
    await db.delete(sessionsTable).where(eq(sessionsTable.id, sessionId));
}
export function setSessionTokenCookie(event: RequestEvent, token: string, expiresAt: Date) {
    event.cookies.set(sessionCookieName, token, {
        httpOnly: true,
        sameSite: 'lax',
        expires: expiresAt,
        path: '/'
    });
}
export function deleteSessionTokenCookie(event: RequestEvent) {
    event.cookies.delete(sessionCookieName, {
        httpOnly: true,
        sameSite: 'lax',
        maxAge: 0,
        path: '/'
    });
}
export type SessionValidationResult =
    | { session: SessionEntity; user: UserEntity }
    | { session: null; user: null };

Este archivo contiene la lógica para gestionar sesiones de usuario en nuestra aplicación. Aquí se realizan operaciones clave como la creación, validación y eliminación de sesiones, así como el manejo de cookies asociadas a ellas. Vamos a desglosar cada parte:

  1. Constantes iniciales:
    • DAY_IN_MS: Representa la cantidad de milisegundos en un día. Esto ayuda a calcular fechas de vencimiento de sesiones.
    • sessionCookieName: Define el nombre de la cookie donde se almacenará el token de sesión. Centralizar este valor facilita los cambios futuros sin modificar múltiples partes del código.
  2. Función generateSessionToken:
    Genera un token único para la sesión utilizando valores aleatorios y codificándolo en formato Base32. Este token será almacenado en la cookie y vinculado a la sesión en la base de datos.
  3. Función createSession:
    • Toma el token generado y un identificador de usuario (userId).
    • Genera un identificador único para la sesión (usando SHA-256 y el token) y calcula su fecha de expiración (30 días desde su creación).
    • Guarda la sesión en la base de datos.
  4. Función validateSessionToken:
    • Verifica si un token de sesión proporcionado es válido.
    • Si el token es válido, obtiene la sesión y el usuario asociado desde la base de datos.
    • Comprueba si la sesión ha expirado:
      • Si ha expirado, elimina la sesión de la base de datos.
      • Si está cerca de expirar (menos de 15 días restantes), extiende su fecha de vencimiento otros 30 días.
    • Retorna los datos de la sesión y del usuario o null si no son válidos.
  5. Función invalidateSession:
    • Elimina una sesión específica de la base de datos según su identificador.
  6. Función setSessionTokenCookie:
    • Establece una cookie segura con el token de sesión, incluyendo propiedades como httpOnly, sameSite, y una fecha de expiración basada en la sesión.
  7. Función deleteSessionTokenCookie:
    • Borra la cookie de sesión al establecerla con una duración maxAge de 0.
  8. Tipo SessionValidationResult:
    • Define el tipo de datos que se devuelve al validar un token de sesión. Puede ser:
      • Una sesión y su usuario asociado.
      • O null para ambos, si la sesión no es válida o no existe.

El siguiente paso es editar el archivo src/app.d.ts. Este es un archivo que SvelteKit utiliza como punto central para definir tipos globales relacionados con la aplicación. Lo abrimos y lo modificamos para que quede de esta forma:

// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
    namespace App {
        interface Locals {
            user: import('$lib/server/db/auth').SessionValidationResult['user'];
            session: import('$lib/server/db/auth').SessionValidationResult['session'];
        }
    }
}
export {};

Ahora que hemos añadido user y session a la interface Locals, podremos acceder a ellas desde las distintas partes de nuestro proyecto mediante event.locals. A continuación veremos como usarlo para que quede más claro.

Una vez hecho esto, vamos a crear el archivo src\hooks.server.ts. Este se ejecutará en el servidor cada vez que hagamos una petición a nuestro servidor. Una vez creado añadimos el siguiente contenido:

import { deleteSessionTokenCookie } from '$lib/server/db/auth';
import { setSessionTokenCookie } from '$lib/server/db/auth';
import { sessionCookieName, validateSessionToken } from '$lib/server/db/auth';
import { redirect, type Handle } from '@sveltejs/kit';
const handleAuth: Handle = async ({ event, resolve }) => {
    const sessionToken = event.cookies.get(sessionCookieName);
    if (!sessionToken) {
        event.locals.user = null;
        event.locals.session = null;
        return resolve(event);
    }
    const { session, user } = await validateSessionToken(sessionToken);
    if (session) {
        setSessionTokenCookie(event, sessionToken, session.expiresAt);
        const restrictedRoutes = ['/login', '/registro'];
        if (session && restrictedRoutes.includes(event.url.pathname)) {
            throw redirect(303, '/');
        }
    } else {
        deleteSessionTokenCookie(event);
    }
    event.locals.user = user;
    event.locals.session = session;
    return resolve(event);
};
export const handle: Handle = handleAuth;

En este archivo implementamos un middleware de SvelteKit que maneja la autenticación para cada solicitud. Vamos paso a paso:

1. Importaciones

Importamos las funciones necesarias para manejar sesiones y cookies (setSessionTokenCookie, deleteSessionTokenCookie, validateSessionToken, sessionCookieName) desde $lib/server/db/auth. También traemos redirect y el tipo Handle desde @sveltejs/kit.

2. Creación del middleware handleAuth

handleAuth es una constante de tipo Handle (un middleware en SvelteKit) que recibe dos parámetros principales:

  • event: Representa la solicitud entrante, incluyendo datos como cookies, URL y locales.
  • resolve: Una función que continúa el procesamiento normal de la solicitud.

3. Recuperación del token de sesión

Utilizamos event.cookies.get(sessionCookieName) para obtener el token de sesión almacenado en las cookies del cliente.

  • Si no existe un token de sesión:
    • Se define event.locals.user y event.locals.session como null. Estas propiedades fueron definidas en el archivo app.d.ts y se utilizan para almacenar información sobre el usuario autenticado y la sesión.
    • Llamamos a resolve(event) para continuar con la solicitud, ya que no necesitamos realizar más validaciones.

4. Validación de la sesión

Si el token de sesión existe, llamamos a validateSessionToken(sessionToken) para verificar si es válido y obtener la información asociada:

  • Si la sesión es válida:
    • Actualizamos la cookie de sesión con una nueva fecha de expiración mediante setSessionTokenCookie(event, sessionToken, session.expiresAt).
    • Comprobamos si el usuario intenta acceder a rutas restringidas para usuarios autenticados, como /login o /registro. Si es así, redirigimos al usuario a la página principal (/) usando redirect(303, '/').
  • Si la sesión no es válida:
    • Eliminamos la cookie de sesión llamando a deleteSessionTokenCookie(event).

5. Asignación de información local

Independientemente de si la sesión es válida o no:

  • Asignamos el usuario (user) y la sesión (session) a las propiedades event.locals.user y event.locals.session, respectivamente. Esto permite que otras partes de la aplicación accedan a esta información.

6. Resolución de la solicitud

Finalmente, llamamos a resolve(event) para continuar el flujo de la aplicación.

7. Exportación del middleware

Exportamos handleAuth como handle, que es la forma en que SvelteKit reconoce este middleware y lo aplica automáticamente.

Página de login

Utilidades para las contraseñas y para los usuarios de la base de datos

En esta parte del tutorial, vamos a renombrar el archivo src\lib\server\db\posts.ts a src\lib\server\db\db-posts.ts. Este cambio tiene dos propósitos principales:

  1. Mayor claridad: El nuevo nombre aporta más contexto sobre el propósito del archivo, indicando que está relacionado específicamente con la base de datos (DB). Esto facilita la comprensión del código y su estructura.
  2. Evitar colisiones: Dado que el término "post" se utiliza frecuentemente en otras partes del proyecto, cambiar el nombre del archivo ayuda a evitar confusiones o conflictos entre los nombres de archivos, variables o funciones en el futuro.

Lo siguiente que vamos a hacer es crear un archivo llamado src\lib\utils\password-utils.ts. En él guardaremos las funciones que creamos en src\lib\server\db\create-user-admin.ts para encriptar y verificar una contraseña. El archivo debería quedar así:

import { hash, verify } from '@node-rs/argon2';
export async function hashPassword(password: string): Promise<string> {
    return await hash(password);
}
export async function verifyPasswordHash(hash: string, password: string): Promise<boolean> {
    return await verify(hash, password);
}

También puedes borrar las funciones de src\lib\server\db\create-user-admin.ts e importarlas desde este archivo para evitar código duplicado.

Una vez hecho esto y al igual que hicimos para los posts, vamos a crear un archivo llamado src\lib\server\db\db-users.ts que contendrá las distintas consultas a la tabla de usuarios. Este archivo tendrá el siguiente contenido:

import { eq } from 'drizzle-orm';
import { db } from './db';
import { usersTable, type UserEntity } from './schema';
import { verifyPasswordHash } from '$lib/utils/password-utils';
export async function getUserByEmailAndPassword(
    email: string,
    rawPassword: string
): Promise<UserEntity> {
    const getUsers = await db.select().from(usersTable).where(eq(usersTable.email, email));
    if (!getUsers[0]) {
        throw new Error('User not exists');
    }
    if (!verifyPasswordHash(getUsers[0].password, rawPassword)) {
        throw new Error('Invalid password');
    }
    return getUsers[0];
}

De momento, este archivo contiene únicamente la función getUserByEmailAndPassword. Esta función realiza las siguientes tareas:

  1. Recibe las credenciales del usuario: Se le pasan como parámetros el correo electrónico (email) y la contraseña sin procesar (rawPassword).
  2. Busca al usuario en la base de datos: Utiliza Drizzle ORM para consultar la tabla de usuarios (usersTable) y encontrar un registro que coincida con el correo proporcionado.
    • Si no se encuentra ningún usuario con ese correo, lanza una excepción con el mensaje "User not exists".
  3. Valida la contraseña: Compara la contraseña proporcionada (rawPassword) con el hash de la contraseña almacenada en la base de datos.
    • Si la contraseña no coincide, lanza una excepción con el mensaje "Invalid password".
  4. Devuelve los datos del usuario: Si ambas verificaciones (correo y contraseña) son exitosas, retorna el registro del usuario encontrado.

Creación de la página de login

Para la creación del login vamos a crear dos componentes nuevos que podremos reutilizar en el resto de la página. Para el primero, crea un archivo llamado src\lib\components\Button.svelte con el siguiente código:

<script lang="ts">
    import type { Snippet } from 'svelte';
    interface ButtonProps {
        children: Snippet;
        onclick: ((e: MouseEvent) => void) | (() => void);
        isDisabled: boolean;
        className?: string;
    }
    let { children, className, isDisabled, ...props }: ButtonProps = $props();
</script>
<button class={`btn ${className}`} class:btn-disabled={isDisabled} {...props}>
    {@render children()}
</button>
<style>
    .btn {
        font-size: 1.2rem;
        padding: 0.6rem;
        width: 100%;
        font-weight: 700;
        background-color: #347cbb;
        color: #fff;
        border: none;
        border-radius: 5px;
        cursor: pointer;
        transition: 0.3s;
    }
    .btn:hover {
        background-color: #ffaa00;
    }
    .btn-disabled {
        background-color: #757575;
        border-color: #8b8b8b;
        cursor: not-allowed;
    }
    .btn-disabled:hover {
        background-color: #757575;
        border-color: #8b8b8b;
        cursor: not-allowed;
    }
</style>

Este componente define un botón reutilizable con las siguientes características:

  1. Propiedades:
    • children: Contenido dinámico que se renderiza dentro del botón (puede ser texto o un fragmento más complejo).
    • onclick: Una función que se ejecutará cuando el botón sea pulsado, puede recibir un evento MouseEvent o no.
    • isDisabled: Indica si el botón está deshabilitado.
    • className (opcional): Permite añadir clases CSS adicionales al botón.
  2. Estructura:
    • El botón usa la clase base btn y opcionalmente combina las clases dinámicas pasadas por className.
    • Si isDisabled es verdadero, se añade dinámicamente la clase btn-disabled.
    • Se utilizan las propiedades restantes (...props) para permitir atributos adicionales como aria-label o data-*.

Por último, definimos los estilos del botón.

El siguiente componente se llamará src\lib\components\MessageError.svelte y tendrá el siguiente contenido:

<script lang="ts">
    interface MessageErrorProps {
        message: string;
    }
    let { message }: MessageErrorProps = $props();
</script>
<div class="error-message">{message}</div>
<style>
    .error-message {
        background-color: #f8d7da;
        color: #842029;
        padding: 15px;
        border: 1px solid #f5c2c7;
        border-radius: 5px;
        font-size: 14px;
        margin: 10px 0;
    }
</style>

Simplemente, es un div con estilos para indicar que es un error y que recibe el mensaje de error.
Como siempre, añade los exports en el archivo src\lib\components\index.ts para poder acceder a ellos de una forma más estructurada.

export { default as Button } from '$components/Button.svelte';
export { default as MessageError } from '$components/MessageError.svelte';

El siguiente paso es editar el archivo src\routes\login\+page.svelte que será la página del login. En él vamos a añadir el siguiente contenido:

<script lang="ts">
    import { Button } from '$components';
    import MessageError from '$components/MessageError.svelte';
	import type { ActionData } from './$types';
	let { form }: { form: ActionData } = $props();
    let email: string = $state('');
    let password: string = $state('');
    let messageError: string | undefined = $state(form?.error);
    function send(event: MouseEvent) {
        if (email.trim().length === 0 || password.trim().length === 0) {
            event.preventDefault();
            return;
        }
    }
    function clearError() {
        messageError = undefined;
    }
</script>
<h1>Login</h1>
<form method="POST">
    <div>
        <label for="email">Email</label>
        <input name="email" type="email" bind:value={email} onfocus={clearError} />
    </div>
    <div>
        <label for="password">Contraseña</label>
        <input name="password" type="password" bind:value={password} onfocus={clearError} />
    </div>
    <Button onclick={send} isDisabled={email.trim().length === 0 || password.trim().length === 0}
        >Login</Button
    >
    {#if messageError}
        <MessageError message={messageError} />
    {/if}
</form>
<style>
    form {
        max-width: 400px;
        margin: 0 auto;
    }
    form div {
        display: flex;
        flex-direction: column;
    }
    label {
        text-align: left;
        width: 100%;
        margin-bottom: 0.5rem;
    }
    input {
        font-size: 1.2rem;
        width: 100%;
        padding: 0.5rem;
        border-radius: 5px;
        border: 1px solid #999999;
        background-color: #f0f0f0;
        margin-bottom: 1rem;
    }
</style>

Este código implementa el formulario de login con una validación básica, manejo de errores y estilos personalizados:

  1. Importaciones:
    • Se importan los componentes Button y MessageError que creamos anteriormente.
    • También se importa ActionData, que se usa para manejar posibles errores del formulario desde $props.
  2. Variables:
    • email y password almacenan los valores de los campos del formulario y se inicializan con $state para permitir la reactividad.
    • messageError guarda el mensaje de error si ocurre un fallo en el login, recuperado de form?.error.
  3. Funciones:
    • send: Previene el envío del formulario si los campos email o password están vacíos.
    • clearError: Borra el mensaje de error cuando el usuario tiene el foco en cualquiera de los campos de entrada.
  4. HTML:
    • El formulario incluye dos entradas, vinculadas a las variables reactivas mediante bind:value. Esto asegura que los valores de email y password reflejen el contenido del input en tiempo real.
    • Al hacer foco en un campo, se llama a clearError para eliminar el mensaje de error.
    • El componente Button valida si los campos están completos y deshabilita el botón si no lo están.
    • Si hay un error, se muestra el componente MessageError con el mensaje correspondiente.
  5. Estilos:
    • Por último definimos los estilos del formulario.

Una vez hecho esto, vamos a crear el archivo src\routes\login\+page.server.ts que se encargará de recibir el formulario y realizar las validaciones pertinentes y crear la sesión si todo ha ido bien:

import { getUserByEmailAndPassword } from '$lib/server/db/db-users';
import { fail, redirect } from '@sveltejs/kit';
import type { PageServerLoad, Actions } from './$types';
import {
    createSession,
    generateSessionToken,
    sessionCookieName,
    setSessionTokenCookie,
    validateSessionToken
} from '$lib/server/db/auth';
export const load: PageServerLoad = async ({ cookies }) => {
    const infoSession = await validateSessionToken(cookies.get(sessionCookieName));
    if (infoSession.session !== null) {
        redirect(302, '/');
    }
};
export const actions = {
    default: async (event) => {
        const data = await event.request.formData();
        if (!data.get('email') || !data.get('password')) {
            return fail(400, { error: 'Debes enviar el email y la contraseña' });
        }
        const email = convertToString(data.get('email'));
        const password = convertToString(data.get('password'));
        try {
            const user = await getUserByEmailAndPassword(email, password);
            if (!user.isActive) {
                return fail(400, {
                    error:
                        'Tu usuario ha sido bloqueado, por favor ponte en contacto con administración para obtener más detalles'
                });
            }
            const token = await generateSessionToken();
            const dataSession = await createSession(token, user.id);
            setSessionTokenCookie(event, token, dataSession.expiresAt);
            return { success: true };
        } catch (err) {
            return fail(400, { error: 'Email o contraseña incorrectos' });
        }
    }
} satisfies Actions;
function convertToString(value: FormDataEntryValue | null): string {
    return value ? value.toString() : '';
}

Este archivo realiza las siguientes acciones:

  1. Importaciones:
    • Se importan funciones y constantes necesarias para la autenticación (getUserByEmailAndPassword, validateSessionToken, createSession, etc.), y métodos de SvelteKit como fail y redirect.
  2. Verificación de sesión (load):
    • La función load se ejecuta en el lado del servidor cuando se carga la página.
    • Valida si el usuario tiene una sesión activa mediante la cookie de sesión.
    • Si la sesión es válida, redirige al usuario a la página de inicio (/). Esto evita que los usuarios autenticados accedan a la página de login innecesariamente.
  3. Manejo de acciones (actions):
    • El objeto actions define la lógica para procesar el formulario de login.
    • La acción default extrae el email y la contraseña del formulario enviado.
      • Si faltan datos, devuelve un error con código 400 y un mensaje indicando que los campos son obligatorios.
      • Los valores del formulario se convierten a cadena mediante la función auxiliar convertToString.
  4. Autenticación del usuario:
    • Se verifica si el email y la contraseña coinciden con un usuario registrado utilizando getUserByEmailAndPassword.
    • Si las credenciales son incorrectas, se devuelve un error genérico sin especificar si falló por email o contraseña, añadiendo una capa de seguridad contra ataques de fuerza bruta.
    • Si el usuario existe pero está desactivado, se devuelve un mensaje personalizado indicando que el usuario está bloqueado.
  5. Creación de sesión:
    • Si las credenciales son válidas y el usuario está activo:
      • Se genera un token de sesión con generateSessionToken.
      • Se almacena la sesión en la base de datos mediante createSession.
      • La cookie de sesión se configura en la respuesta con setSessionTokenCookie.
    • Finalmente, se devuelve un objeto con success: true para indicar que la autenticación fue exitosa.
  6. Función auxiliar convertToString:
    • Convierte los valores del formulario en cadenas para evitar valores nulos o inválidos.

Link para cerrar sesión

Ahora vamos a crear la ruta /logout para poder cerrar sesión. Para ello, creamos el archivo src\routes\logout\+page.server.ts y añadimos el siguiente código:

import { redirect } from '@sveltejs/kit';
import { deleteSessionTokenCookie } from '$lib/server/db/auth';
import { invalidateSession } from '$lib/server/db/auth';
import type { Actions } from './$types';
export const actions = {
    default: async (event) => {
        const sessionId = event.locals.session?.id;
        if (!sessionId) {
            redirect(302, '/');
        }
        invalidateSession(sessionId);
        deleteSessionTokenCookie(event);
        event.locals.session = null;
        event.locals.user = null;
        redirect(302, '/');
    }
} satisfies Actions;

Este código recibirá un formulario y se encargará de eliminar la sesión si existe de la base de datos y de las cookies. Por último redirige a la página de inicio.

Mostar usuario y botón de cerrar sesión en la barra de navegación si estamos autenticados

Cuando estemos autenticados, vamos a querer mostrar en la barra de navegación el nombre del usuario y el botón de cerrar sesión. Para ello, abrimos el archivo src\lib\components\layout\Header.svelte y lo editamos con el siguiente contenido:

<script lang="ts">
    import Menu from '$assets/menu.png';
    import Logo from '$assets/logo.png';
    import HeaderImg from '$assets/header.png';
    import { page } from '$app/stores';
    interface HeaderProps {
        username?: string;
    }
    let { username }: HeaderProps = $props();
</script>
<header>
    <div class="content-nav">
        <div class="menu container">
            <a href="/" class="logo">
                <img src={Logo} alt="Logo" />
            </a> <input type="checkbox" id="menu" />
            <label for="menu">
                <img src={Menu} class="menu-icon" alt="Menu responsive" />
            </label>
            <nav class="navbar">
                <ul>
                    <li class:border-navbar={$page.url.pathname === '/'}><a href="/">Inicio</a></li>
                    <li class:border-navbar={$page.url.pathname === '/contacto'}>
                        <a href="/contacto">Contacto</a>
                    </li>
                    {#if username}
                        <li class:border-navbar={$page.url.pathname === '/mi-cuenta'}>
                            <a href="/">Hola, {username}</a>
                        </li>
                        <li class:border-navbar={$page.url.pathname === '/logout'}>
                            <form action="/logout" method="post">
                                <button>Logout</button>
                            </form>
                        </li>
                    {:else}
                        <li class:border-navbar={$page.url.pathname === '/login'}>
                            <a href="/login">Login</a>
                        </li>
                        <li class:border-navbar={$page.url.pathname === '/registro'}>
                            <a href="/registro">Registro</a>
                        </li>
                    {/if}
                </ul>
            </nav>
        </div>
    </div>
    <div class="content-second-header">
        <a href="/">
            <img id="image-header" src={HeaderImg} alt="Cabecera cosasdedevs" />
        </a>
        {#if $page.url.pathname === '/'}
            <h1>Artítulos y tutoriales del mundo tech</h1>
        {/if}
    </div>
</header>
<style>
    .container {
        max-width: 1000px;
        margin: 0 auto;
    }
    .content-second-header {
        display: flex;
        flex-direction: column;
        align-items: center;
        margin: 3rem 0;
        padding: 0 10px;
    }
    .content-second-header img {
        max-width: 100%;
    }
    .content-second-header h1 {
        margin-top: 1rem;
        text-align: center;
    }
    .content-nav {
        background-color: black;
        min-height: 61px;
    }
    .menu {
        position: absolute;
        left: 0;
        right: 0;
        display: flex;
        align-items: center;
        justify-content: space-between;
    }
    .logo {
        font-size: 25px;
        color: #f7a800;
        text-transform: uppercase;
        font-weight: bold;
    }
    .logo img {
        width: 40px;
    }
    .menu .navbar ul li {
        position: relative;
        float: left;
        transition: 0.3s;
    }
    .border-navbar {
        box-shadow: inset 0px -10px 0px 0px #ffffff; /* Sombra dentro del elemento */
    }
    .menu .navbar ul li:hover {
        background-color: white;
    }
    .menu .navbar ul li a {
        font-size: 18px;
        padding: 20px;
        color: white;
        display: block;
        font-weight: bold;
        transition: 0.3s;
    }
    .menu .navbar ul li a:hover {
        color: #0f0f0f;
    }
    .menu .navbar ul li form button {
        font-size: 18px;
        padding: 20px;
        color: white;
        border: none;
        background: none;
        display: block;
        font-weight: bold;
        transition: 0.3s;
        cursor: pointer;
        font-family: 'Open Sans', sans-serif;
    }
    .menu .navbar ul li form button:hover {
        color: #0f0f0f;
    }
    #menu {
        display: none;
    }
    .menu-icon {
        width: 25px;
    }
    .menu label {
        cursor: pointer;
        display: none;
    }
    @media (max-width: 800px) {
        .content-second-header h1 {
            font-size: 1.2rem;
        }
        .content-nav {
            min-height: 70px;
        }
        .menu {
            top: 0;
            padding: 20px;
        }
        .menu label {
            display: initial;
        }
        .menu .navbar {
            position: absolute;
            top: 100%;
            left: 0;
            right: 0;
            background-color: black;
            display: none;
        }
        .menu .navbar ul li {
            width: 100%;
        }
        #menu:checked ~ .navbar {
            display: initial;
        }
    }
</style>

En este archivo ahora opcionalmente vamos a poder recibir el nombre de usuario. Si es así, en vez de mostrar el botón de login y logout, mostraremos un Hola, nombre de usuario y cerrar sesión.
Para que podamos recibir el usuario vamos a tener que crear el archivo src\routes\+layout.server.ts. Este archivo all igual que +layout.svelte, se ejecutará para todas las páginas pero en el lado del servidor. Este archivo tendrá el siguiente contenido:

import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ locals }) => {
    return {
        layoutData: {
            username: locals.user?.username
        }
    };
};

Lo que hace simplemente es que si tenemos el nombre de usuario lo devolvemos para poder recuperarlo desde +layout.svelte.

Por útlimo, editamos el archivo src\routes\+layout.svelte para poder enviar el nombre de usuario al componente Header:

<script lang="ts">
    import { page } from '$app/stores';
    import { Header } from '$components';
    import type { Snippet } from 'svelte';
    import '../app.css';
    let { children }: { children: Snippet } = $props();
    const username = $page.data.layoutData.username;
</script>
<Header {username} />
<main class="main-container">
    {@render children()}
</main>

Y eso es todo por este tutorial. Recordad que cualquier duda podéis escribirla en la caja de comentarios y también os dejo el enlace a la rama que corresponde a este tutorial por si tenéis cualquier problema:

https://github.com/albertorc87/blog-svelte-5/tree/tutorial-5-login-y-sesion

Espero que este post te ayude y como siempre, te recomiendo seguirme en Twitter para estar al tanto de los nuevo contenido. Ahora también puedes seguirme en Instagram donde estoy subiendo tips, tutoriales en vídeo e información sobre herramientas para developers.

Por último os dejo mi guía para aprender a trabajar con APIs donde explico todo el funcionamiento de una API, el protocolo HTTP y veremos como construir una API con arquitectura REST.

Nos leemos 👋.

857 vistas

🐍 Sígueme en Twitter

Si te gusta el contenido que subo y no quieres perderte nada, sígueme en Twitter y te avisaré cada vez que cree contenido nuevo 💪
Luego ¡Te sigo!

Nos tomamos en serio tu privacidad

Utilizamos cookies propias y de terceros para recopilar y analizar datos sobre la interacción de los usuarios con cosasdedevs.com. Ver política de cookies.