SvelteKit Surreal Database Authentication

TL;DR

I created a login for Surreal Database and SvelteKit. The core server function can be reused in ANY TS SSR Framework!

Setup

Create a new SvelteKit project with TS.

npx sv create surreal-auth

Install latest versio…


This content originally appeared on DEV Community and was authored by Jonathan Gamble

SvelteKit Surreal Login

TL;DR

I created a login for Surreal Database and SvelteKit. The core server function can be reused in ANY TS SSR Framework!

Setup

Create a new SvelteKit project with TS.

npx sv create surreal-auth

Install latest version of Surreal JS. I am not using alpha for this demo.

npm i -D surrealdb

Surreal JS SDK

Unfortunately, you must manually handle errors with try and catch. I am hoping to get this fixed with destructuring. Here I created some helper functions to remedy this and put all the database functions in one place.

Connect

export async function surrealConnect({
    namespace,
    database,
    url
}: {
    namespace: string,
    database: string,
    url: string
}) {

    const db = new Surreal();

    try {

        await db.connect(url, {
            namespace,
            database
        });

    } catch (e) {

        if (e instanceof SurrealDbError) {
            return {
                data: null,
                error: e
            };
        }

        if (e instanceof Error) {
            return {
                data: null,
                error: e
            };
        }

        return {
            data: null,
            error: new Error('Unknown error during SurrealDB connection')
        };
    }
    return {
        data: db,
        error: null
    };
}

Login

export async function surrealLogin({
    db,
    namespace,
    database,
    username,
    password
}: {
    db: Surreal,
    namespace: string,
    database: string,
    username: string,
    password: string
}) {

    try {

        const signinData = await db.signin({
            namespace,
            database,
            variables: {
                username,
                password
            },
            access: 'user'
        });

        return {
            data: signinData,
            error: null
        };

    } catch (e) {

        if (e instanceof SurrealDbError) {
            return {
                data: null,
                error: e
            };
        }

        if (e instanceof Error) {
            return {
                data: null,
                error: e
            };
        }

        return {
            data: null,
            error: new Error('Unknown error during login')
        };
    }
};

Register

export async function surrealRegister({
    db,
    namespace,
    database,
    username,
    password
}: {
    db: Surreal,
    namespace: string,
    database: string,
    username: string,
    password: string
}) {

    try {

        const signupData = await db.signup({
            namespace,
            database,
            variables: {
                username,
                password
            },
            access: 'user'
        });

        return {
            data: signupData,
            error: null
        };

    } catch (e) {

        if (e instanceof SurrealDbError) {
            return {
                data: null,
                error: e
            };
        }

        if (e instanceof Error) {
            return {
                data: null,
                error: e
            };
        }

        return {
            data: null,
            error: new Error('Unknown error during registration')
        };
    }
};

Change Password

export async function surrealChangePassword({
    db,
    currentPassword,
    newPassword,
    userId
}: {
    db: Surreal,
    currentPassword: string,
    newPassword: string,
    userId: string
}) {

    try {

        const query = `
            UPDATE $id
            SET password = crypto::argon2::generate($new)
            WHERE crypto::argon2::compare(password, $old)
        `;

        const [result] = await db.query<[{
            id: string,
            password: string,
            username: string
        }][]>(query, {
            id: new StringRecordId(userId),
            old: currentPassword,
            new: newPassword
        });

        if (!result) {
            return {
                data: null,
                error: new Error("Password change failed")
            };
        }

        return {
            data: result[0],
            error: null
        };

    } catch (error) {

        if (error instanceof SurrealDbError) {
            return {
                data: null,
                error
            };
        }

        if (error instanceof Error) {
            return {
                error,
                data: null
            };
        }
        return {
            error: new Error('Unknown query error'),
            data: null
        };
    }
}

📝 You need to use the correct namespace, database, and access user when performing surreal functions.

Surreal Server

I wanted this function to be reusable in any framework, with inspiration from supabase-ssr. We can setup our server function to handle cookies and our credentials.

export function surrealServer({
    cookies: {
        cookieName,
        setCookie,
        getCookie
    },
    credentials: {
        url,
        namespace,
        database
    }
}: {
    cookies: {
        cookieName?: string,
        setCookie: SetCoookieFn,
        getCookie: GetCookieFn
    },
    credentials: {
        url: string,
        namespace: string,
        database: string
    }
}) {

    const tokenName = cookieName || 'surreal_token';

    const surrealToken = getCookie(tokenName);
...

Connect Wrapper

async function connect() {

    const { data: db, error: connectError } = await surrealConnect({
        namespace,
        database,
        url
    });

    if (connectError) {
        return {
            data: null,
            error: connectError
        };
    }

    if (surrealToken) {

        await db.authenticate(surrealToken);

        return {
            data: db,
            error: null
        };
    }

    // No token, ensure logged out

    logout();

    return {
        data: db,
        error: null
    };
}

📝 Check for token, and authenticate if there is one.

Login and Register

async function login(username: string, password: string) {

    logout();

    const { data: db, error: dbError } = await connect();

    if (dbError) {
        return {
            db: null,
            error: dbError
        };
    }

    const {
        data: token,
        error: loginError
    } = await surrealLogin({
        db,
        namespace,
        database,
        username,
        password
    });

    if (loginError) {
        return {
            db: null,
            error: loginError
        };
    }

    setCookie(
        tokenName,
        token,
        TOKEN_COOKIE_OPTIONS
    );

    return {
        db,
        error: null
    };
};

async function register(username: string, password: string) {

    logout();

    const { data: db, error: dbError } = await connect();

    if (dbError) {
        return {
            db: null,
            error: dbError
        };
    }

    const {
        data: token,
        error: registerError
    } = await surrealRegister({
        db,
        namespace,
        database,
        username,
        password
    });

    if (registerError) {
        return {
            db: null,
            error: registerError
        };
    }

    setCookie(
        tokenName,
        token,
        TOKEN_COOKIE_OPTIONS
    );

    return {
        db,
        error: null
    };
};

📝 Register or Login then save the token. This is a cookie wrapper function that you can set to work in any framework.

Change Password

async function changePassword(oldPassword: string, newPassword: string) {

    const userId = getUser();

    if (!userId) {
        return {
            data: null,
            error: new Error('Not authenticated')
        };
    }

    const { data: db, error: dbError } = await connect();

    if (dbError) {
        return {
            data: null,
            error: dbError
        };
    }

    const { data, error: changeError } = await surrealChangePassword({
        db,
        currentPassword: oldPassword,
        newPassword,
        userId
    });


    if (changeError) {
        return {
            data: null,
            error: changeError
        };
    }

    return {
        data,
        error: null
    };
}

Logout

function logout() {

    const token = getCookie(tokenName);

    if (token) {
        // delete cookie equivalent
        setCookie(tokenName, '', {
            ...TOKEN_COOKIE_OPTIONS,
            maxAge: 0            
        });
    }
};

📝 We can delete a cookie by setting it to expire. No need for an extraneous deleteCookie function.

Get User

function getUser() {

    const token = getCookie(tokenName);

    if (!token) {
        return null;
    }

    return decodeJwt(token).ID as string;
}

async function getUserInfo() {

    const { data: db, error: dbError } = await connect();

    if (dbError) {
        return null;
    }

    const info = await db.info();

    if (!info?.id) {
        return null;
    }

    return info.id.toString();
}

📝 We DO NOT want to make an extraneous call to the database for every server call, but on secure queries, like change password, we do. To avoid extra fetching, we can decode the JWT and use getUser, otherwise, we can get the user data directly from the database with getUserInfo.

export function decodeJwt(token: string) {
    try {
        const [, payloadB64] = token.split('.');
        return JSON.parse(
            Buffer.from(payloadB64, 'base64url').toString()
        ) as JwtPayload;
    } catch {
        return {};
    }
}

📝 We need try and catch to prevent a problem with JSON.parse() or Buffer in case of data corruption.

Server Hook

For SvelteKit, we can use the hooks.server.ts file to allow our surreal server to be available everywhere.

import type { Handle } from '@sveltejs/kit';
import {
    PRIVATE_SURREALDB_URL,
    PRIVATE_SURREALDB_NAMESPACE,
    PRIVATE_SURREALDB_DATABASE
} from '$env/static/private';
import { surrealServer } from './lib/surreal/surreal-server';

const config = {
    url: PRIVATE_SURREALDB_URL,
    namespace: PRIVATE_SURREALDB_NAMESPACE,
    database: PRIVATE_SURREALDB_DATABASE
};

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

    event.locals.surreal = surrealServer({
        cookies: {
            setCookie: (name, value, options) => 
                event.cookies.set(name, value, options),
            getCookie: (name) => event.cookies.get(name)
        },
        credentials: {
            url: config.url,
            namespace: config.namespace,
            database: config.database
        }
    });

    return resolve(event);
};

We use the event in hooks with native cookie functions. This can work in ANY framework.

Remember to update the types.

import type { surrealServer } from '$lib/surreal/surreal-server';

declare global {
    namespace App {
        // interface Error {}
        interface Locals {
            surreal: ReturnType<typeof surrealServer>;
        }
        // interface PageData {}
        // interface PageState {}
        // interface Platform {}
    }
}

export {};

Auth Guards

Hide page from unauthorized users:

import { redirect } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";

export const load: PageServerLoad = async ({ locals: { surreal } }) => {

    const userId = surreal.getUser();

    if (!userId) {
        redirect(303, '/login');
    }
};

Hide Login page from session users:

export const load: PageServerLoad = async ({ locals: { surreal } }) => {

    const userId = surreal.getUser();

    if (userId) {
        redirect(303, '/');
    }
};

Server Actions

    login: async ({ request, locals: { surreal } }) => {

        const formData = await request.formData();

        const { username, password } = Object.fromEntries(formData);

        if (typeof username !== 'string' || typeof password !== 'string') {
            error(500, 'Invalid form data');
        }

        const {
            error: loginError
        } = await surreal.login(username, password);

        if (loginError) {
            return {
                error: loginError.message
            };
        }

        redirect(303, '/');
    },

    logout: async ({ locals: { surreal } }) => {
        surreal.logout();
        redirect(303, '/');
    }

If we're just logging in, we don't need to verify username and password strings. If we did, we could use valibot.

const registerSchema = v.object({
    username: v.pipe(
        v.string(),
        v.minLength(3, 'Username must be at least 3 characters long')
    ),
    password: v.pipe(
        v.string(),
        v.minLength(3, 'Password must be at least 3 characters long')
    )
});

...

export const actions: Actions = {

    register: async ({ request, locals: { surreal } }) => {

        const formData = await request.formData();

        const data = Object.fromEntries(formData);

        const result = v.safeParse(registerSchema, data);

        if (!result.success) {
            error(400, result.issues[0].message);
        }

        const { username, password } = result.output;

        const {
            error: registerError
        } = await surreal.register(username, password);

        if (registerError) {
            error(500, registerError.message);
        }

        redirect(303, '/');
    }
};

Or, we could manually check for something simple.

export const actions: Actions = {

    changePassword: async ({ request, locals: { surreal } }) => {

        const formData = await request.formData();

        const { oldPassword, newPassword } = Object.fromEntries(formData);

        if (typeof oldPassword !== 'string'
            || typeof newPassword !== 'string'
            || newPassword.length < 3) {

            error(500, 'Invalid password data');
        }

        const {
            data: passwordChangeData,
            error: passwordChangeError
        } = await surreal.changePassword(oldPassword, newPassword);

        if (passwordChangeError) {
            return {
                error: passwordChangeError.message
            };
        }

        if (passwordChangeData?.password) {
            return {
                success: true
            };
        }

        return {
            success: true
        };
    }
};

Surreal Alpha is going to start handling REFRESH tokens, but this is the latest stable version for now.

And that's all folks!

Repo: GitHub

J


This content originally appeared on DEV Community and was authored by Jonathan Gamble


Print Share Comment Cite Upload Translate Updates
APA

Jonathan Gamble | Sciencx (2025-11-22T00:58:05+00:00) SvelteKit Surreal Database Authentication. Retrieved from https://www.scien.cx/2025/11/22/sveltekit-surreal-database-authentication-2/

MLA
" » SvelteKit Surreal Database Authentication." Jonathan Gamble | Sciencx - Saturday November 22, 2025, https://www.scien.cx/2025/11/22/sveltekit-surreal-database-authentication-2/
HARVARD
Jonathan Gamble | Sciencx Saturday November 22, 2025 » SvelteKit Surreal Database Authentication., viewed ,<https://www.scien.cx/2025/11/22/sveltekit-surreal-database-authentication-2/>
VANCOUVER
Jonathan Gamble | Sciencx - » SvelteKit Surreal Database Authentication. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/11/22/sveltekit-surreal-database-authentication-2/
CHICAGO
" » SvelteKit Surreal Database Authentication." Jonathan Gamble | Sciencx - Accessed . https://www.scien.cx/2025/11/22/sveltekit-surreal-database-authentication-2/
IEEE
" » SvelteKit Surreal Database Authentication." Jonathan Gamble | Sciencx [Online]. Available: https://www.scien.cx/2025/11/22/sveltekit-surreal-database-authentication-2/. [Accessed: ]
rf:citation
» SvelteKit Surreal Database Authentication | Jonathan Gamble | Sciencx | https://www.scien.cx/2025/11/22/sveltekit-surreal-database-authentication-2/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.