Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Hey! Maybe you've already resolved it and this might not be 100% relevant since I use Firebase to manage auth, but I'll share it there in case someone might need it :)
- So I have a login route (routes/login/+page.svelte). I won't paste full UI code, but the script tag looks like:
- <script>
- import { firebaseAuth } from "$lib/client/firebase.app.js";
- import {
- signInWithEmailAndPassword,
- setPersistence,
- inMemoryPersistence,
- } from "firebase/auth";
- import { goto } from "$app/navigation";
- import { onMount } from "svelte";
- let email = $state("");
- let password = $state("");
- let loginError = $state("");
- let isLoading = $state(false);
- onMount(() => {
- setPersistence(firebaseAuth, inMemoryPersistence);
- });
- async function login() {
- isLoading = true;
- loginError = "";
- if (!email || !password) {
- loginError = "Email and password are required";
- isLoading = false;
- return;
- }
- try {
- const result = await signInWithEmailAndPassword(
- firebaseAuth,
- email,
- password
- );
- if (result?.user) {
- const idToken = await result.user.getIdToken();
- const response = await fetch("/api/login", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({ idToken }),
- });
- if (!response.ok) {
- const errorData = await response.json();
- throw new Error(errorData.message || "Session login failed");
- }
- goto("/");
- } else {
- loginError = "Login successful, but user data not found";
- }
- } catch (error) {
- console.error("Login error:", error);
- if (error instanceof Error) loginError = error.message;
- else loginError = "An unknown error occurred during login.";
- } finally {
- isLoading = false;
- }
- }
- </script>
- So I use Firebase's client sdk to 'sign-in', i.e. check the email and password here on the front end. Then, if successfull, I make a post request to a routes/api/login/+server.js with the firebase idToken in the body. The code for actual login looks like:
- import { getFirebaseAdminAuth } from '$lib/server/firebase.admin.js';
- import { error, json } from '@sveltejs/kit';
- import { dev } from '$app/environment';
- // Constants for cookie settings
- const SESSION_COOKIE_NAME = '__session';
- const EXPIRES_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
- /** @typedef {import('cookie').CookieSerializeOptions} CookieSerializeOptions */
- /**
- * Handles POST requests to create a session cookie.
- * @type {import('@sveltejs/kit').RequestHandler}
- */
- export async function POST({ request, cookies }) {
- const adminAuth = getFirebaseAdminAuth();
- try {
- const body = await request.json();
- const idToken = body.idToken;
- if (!idToken || typeof idToken !== 'string') {
- throw error(400, { message: 'ID token must be provided as a string.' });
- }
- const expiresIn = EXPIRES_IN_MILLISECONDS;
- // Create the session cookie. This verifies the ID token.
- const sessionCookie = await adminAuth.createSessionCookie(idToken, { expiresIn });
- /** @type {CookieSerializeOptions & { path: string }} */
- const options = {
- maxAge: expiresIn / 1000, // maxAge is in seconds
- httpOnly: true,
- secure: !dev, // Use secure cookies in production
- path: '/',
- sameSite: 'lax'
- };
- cookies.set(SESSION_COOKIE_NAME, sessionCookie, options);
- return json({ status: 'success', message: 'Session cookie created successfully.' });
- } catch (err) {
- console.error('Session login error:', err);
- // Check if err is an object with a string 'code' property
- if (
- typeof err === 'object' &&
- err !== null &&
- 'code' in err &&
- typeof err.code === 'string' // Explicitly check if code is a string
- ) {
- // Now we know err.code is a string
- if (err.code === 'auth/argument-error' || err.code === 'auth/invalid-id-token') {
- throw error(401, { message: 'Unauthorized: Invalid ID token.' });
- }
- }
- // Default error for other issues or if err doesn't fit the expected structure
- throw error(500, { message: 'Internal Server Error: Could not create session cookie.' });
- }
- }
- So, as you see, I simply set the cookie which will be saved in the browser. Now, the relevant part for this post. How do you now use this to protect routes and manage user's auth?
- import { getFirebaseAdminAuth } from '$lib/server/firebase.admin';
- // Constants
- const SESSION_COOKIE_NAME = '__session'; // Same name as used in login endpoint
- /**
- * SvelteKit server hook to handle request processing, including session verification.
- * @type {import('@sveltejs/kit').Handle}
- */
- export async function handle({ event, resolve }) {
- const adminAuth = getFirebaseAdminAuth();
- const sessionCookie = event.cookies.get(SESSION_COOKIE_NAME);
- // Initialize locals.user
- event.locals.user = null;
- if (sessionCookie) {
- try {
- // Verify the session cookie. checkRevoked is true to check if the session was revoked.
- const decodedClaims = await adminAuth.verifySessionCookie(sessionCookie, true);
- // Add user info to event.locals for use in pages and endpoints
- event.locals.user = {
- uid: decodedClaims.uid,
- email: decodedClaims.email,
- email_verified: decodedClaims.email_verified,
- // Add other claims as needed (e.g., custom claims)
- // admin: decodedClaims.admin === true, // Example custom claim
- // You can add the full decodedClaims if needed, but often specific fields are sufficient
- // decodedClaims: decodedClaims
- };
- console.log('Session cookie verified successfully for user:', event.locals.user.uid);
- } catch (error) {
- // Session cookie is invalid or revoked. Clear it.
- if (error instanceof Error && 'code' in error) {
- console.warn('Session cookie verification failed:', error?.code, error?.message);
- } else {
- console.warn('Session cookie verification failed:', error);
- }
- event.cookies.delete(SESSION_COOKIE_NAME, { path: '/' });
- event.locals.user = null;
- }
- } else {
- // No session cookie found
- console.log('No session cookie found.');
- }
- // Continue processing the request
- const response = await resolve(event);
- // Optionally add headers here if needed after response is generated
- // response.headers.set('x-custom-header', 'value');
- return response;
- }
- So this hook intercepts any requests in the application and checks the cookie for any route. If successful, it stores the user details in the locals.
- Now you can use this in +layout.server.js:
- import { redirect } from '@sveltejs/kit';
- // List of routes that are publicly accessible without login
- const PUBLIC_ROUTES = ['/login']; // Add other public routes like '/register', '/forgot-password' if needed
- /**
- * Root layout server load function.
- * Checks user authentication status for all routes.
- * Passes user data down to all layouts and pages.
- * @param {{ locals: App.Locals, url: URL }} event
- * @returns {{ user: App.Locals['user'] }}
- */
- export function load({ locals, url }) {
- const { user } = locals; // User object from hooks.server.js (null if not logged in)
- const currentPath = url.pathname;
- // If the user is not logged in AND the current path is not a public route
- if (!user && !PUBLIC_ROUTES.includes(currentPath)) {
- console.log(`Unauthenticated access attempt to ${currentPath}, redirecting to login.`);
- // Preserve the intended destination for redirection after login (optional)
- // const redirectTo = currentPath !== '/' ? `?redirect=${encodeURIComponent(currentPath)}` : '';
- // throw redirect(307, `/login${redirectTo}`);
- throw redirect(307, '/login'); // Redirect to login
- }
- // If the user IS logged in but tries to access login page, redirect to home
- if (user && currentPath === '/login') {
- console.log(`Authenticated user accessing ${currentPath}, redirecting to home.`);
- throw redirect(307, '/'); // Redirect logged-in users away from login
- }
- // Make user data available to all layouts and pages
- // Child `load` functions can access this via `parent()`
- // Page/layout components access it via the `data = $props();`
- return {
- user: user // Pass the user object (or null) to the layout/page data
- };
- }
- This is located in src/routes/, so it will be run first, redirecting you to login if you are not logged in, session cookie was deleted (i.e. you've logged out).
- And for logout, you simply delete the cookie:
- routes/api/logout/+server.js
- import { error, json } from '@sveltejs/kit';
- const SESSION_COOKIE_NAME = '__session'; // Must match the name used elsewhere
- /**
- * Handles POST requests to log the user out by clearing the session cookie.
- * @type {import('@sveltejs/kit').RequestHandler}
- */
- export async function POST({ cookies, locals }) {
- const sessionCookie = cookies.get(SESSION_COOKIE_NAME);
- // Check if the user is actually logged in according to our hook
- // This check is optional but good practice. The main goal is to clear the cookie.
- if (!locals.user || !sessionCookie) {
- // If no user in locals or no cookie, arguably they are already logged out client-side perception wise.
- // We can still attempt to clear any potentially lingering cookie.
- if (sessionCookie) {
- cookies.delete(SESSION_COOKIE_NAME, { path: '/' });
- }
- return json({ status: 'success', message: 'User already logged out or no session found.' });
- }
- try {
- // No need to verify the cookie again here if we trust locals.user set by the hook.
- // We just need to clear the cookie for this session.
- // Delete the session cookie
- cookies.delete(SESSION_COOKIE_NAME, { path: '/' });
- // Log the logout action using the user ID from locals
- console.log('User logged out successfully:', locals.user.uid);
- return json({ status: 'success', message: 'Logout successful.' });
- } catch (err) {
- console.error('Session logout error:', err);
- // Even if deleting the cookie somehow fails, log the error.
- // The primary action (cookie deletion) might still have partially succeeded client-side.
- throw error(500, { message: 'Internal Server Error: Could not process logout.' });
- }
- }
- And you can make a simple button in the UI that, when clicked, does:
- async function logout() {
- await fetch("/api/logout", { method: "POST" });
- // Refresh page or use goto to navigate after logout
- goto("/login");
- }
- Hope this helps :)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement