{"title":"Auth and Databases in Sveltekit","url":"https://seanbehan.ca/posts/svelte-auth","description":"HTTP Basic Auth with a session cookie in SvelteKit, backed by Cloudflare D1.","author":"Sean Behan","published":"2023-11-12T05:40:33.821Z","updated":null,"draft":false,"tags":["cloudflare","javascript","serverless","svelte","sveltekit","typescript"],"readingMinutes":2,"image":null,"sections":[{"id":"introduction","text":"Introduction","level":2},{"id":"project-setup","text":"Project Setup","level":2},{"id":"database-setup","text":"Database Setup","level":2},{"id":"authentication-implementation","text":"Authentication Implementation","level":2},{"id":"checking-authentication-status","text":"Checking Authentication Status","level":2},{"id":"playlist-management","text":"Playlist Management","level":2},{"id":"user-interface","text":"User Interface","level":2},{"id":"conclusion","text":"Conclusion","level":2}],"content_format":"text/markdown","content_url":"https://seanbehan.ca/posts/svelte-auth.md","content":"### Introduction\n\nRecently I implemented HTTP Basic Auth using a session cookie in SvelteKit on\n\nCloudflare Pages using Cloudflare D1 as a database backend. Here I'll show some\n\ncode snippets so you can do the same.\n\n### Project Setup\n\nFirst you would want to set up your project according to\n\n[this](https://developers.cloudflare.com/pages/framework-guides/deploy-a-svelte-site).\n\nSo you can have a basic project set up.\n\n### Database Setup\n\nNext you'll want to add the database to your project. It's easy to create using\n\nwrangler. Just remember to link it to your project in your cloudflare dash\n\nunder settings > functions.\n\n```sh\nwrangler d1 create your-database-name\n```\n\nThen you can copy it into your\n\n[`wrangler.toml`](https://github.com/codebam/svelte-auth/blob/master/wrangler.toml)\n\nso your local dev works with it. To run with support for D1 you can use this\n\noneliner.\n\n```sh\nnpm run build && wrangler pages dev .svelte-kit/cloudflare\n```\n\nMy final schema looks like this. Yours might be a bit different, but mine\n\nallows for adding songs to a playlist, which was the purpose of\n\n[that project](https://github.com/codebam/svelte-auth).\n\n```sql\nCREATE TABLE IF NOT EXISTS Users (id TEXT PRIMARY KEY, password TEXT);\nCREATE TABLE IF NOT EXISTS Playlist (id TEXT PRIMARY KEY, email TEXT, url TEXT, date TEXT);\n```\n\nYou'll want to create these tables using wrangler both locally (on your dev\n\nserver) and remotely.\n\n```sh\nwrangler d1 execute svelte-auth --local --file=./schema.sql\nwrangler d1 execute svelte-auth --file=./schema.sql\n```\n\nNow you can add it to your\n\n[`app.d.ts`](https://github.com/codebam/svelte-auth/blob/master/src/app.d.ts)\n\nlike this.\n\n```typescript\ndeclare global {\n\tnamespace App {\n\t\tinterface Platform {\n\t\t\tenv?: {\n\t\t\t\tDB: D1Database;\n\t\t\t};\n\t\t\tcontext: {\n\t\t\t\twaitUntil(promise: Promise<any>): void;\n\t\t\t};\n\t\t\tcaches: CacheStorage & { default: Cache };\n\t\t}\n\t}\n}\n\nexport {};\n```\n\n### Authentication Implementation\n\nNow you're ready to set up authentication. It's probably easier to just show\n\nyou how I did it. I imported `sha256` which is just a simple function I wrote\n\nto call web crypto to get the sha256sum of a string.\n\n[Here](https://github.com/codebam/svelte-auth/blob/master/src/routes/login/%2Bpage.server.ts)\n\nis my code.\n\nI added [form actions](https://kit.svelte.dev/docs/form-actions). These\n\nallow you to quickly write code that will work with HTML `<form>`'s.\n\n```typescript\nimport { redirect } from '@sveltejs/kit';\nimport sha256 from '$lib/sha256';\n\nexport const actions = {\n\tregister: async (event) => {\n\t\tconst data = await event.request.formData();\n\t\tconst email = data.get('email');\n\t\tconst password = await sha256(data.get('password')?.toString() ?? '');\n\t\tconst { success } = await event.platform?.env?.DB.prepare('INSERT INTO Users VALUES (?, ?)')\n\t\t\t.bind(email, password)\n\t\t\t.all();\n\t\tif (success) {\n\t\t\tevent.cookies.set('session', JSON.stringify({ email, password }));\n\t\t\tthrow redirect(303, '/');\n\t\t}\n\t},\n\tlogin: async (event) => {\n\t\tconst data = await event.request.formData();\n\t\tconst email = data.get('email');\n\t\tconst password = await sha256(data.get('password')?.toString() ?? '');\n\t\tconst results = await event.platform?.env?.DB.prepare('SELECT password FROM Users WHERE id=?')\n\t\t\t.bind(email)\n\t\t\t.all();\n\t\tif (results.results[0].password === password) {\n\t\t\tevent.cookies.set('session', JSON.stringify({ email, password }));\n\t\t\tthrow redirect(303, '/');\n\t\t}\n\t}\n};\n```\n\n### Checking Authentication Status\n\nNow this is enough to save a cookie with the user auth, but to re-auth on a\n\npage we need to check if their cookie is valid. So how I do this is I wrote\n\nanother function called\n\n[`tryLogin.ts`](https://github.com/codebam/svelte-auth/blob/master/src/lib/tryLogin.ts).\n\n```typescript\nimport type { D1Database } from '@cloudflare/workers-types';\n\nconst tryLogin = async (session_cookie: string | undefined, DB: D1Database) => {\n\tif (session_cookie) {\n\t\tconst { email, password } = JSON.parse(session_cookie);\n\t\tconst results = await DB.prepare('SELECT password FROM Users WHERE id=?').bind(email).all();\n\t\tif (results.results[0].password === password) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n};\n\nexport default tryLogin;\n```\n\nNow I can call this inside\n\n[`+page.server.ts`](https://github.com/codebam/svelte-auth/blob/master/src/routes/%2Bpage.server.ts).\n\n```typescript\nimport tryLogin from '$lib/tryLogin';\nimport getCurrentSong from '$lib/getCurrentSong';\n\nexport const load = async (event) => ({\n\tauth: await tryLogin(event.cookies.get('session'), event.platform?.env?.DB),\n\tsong: await getCurrentSong(event.platform?.env?.DB)\n});\n```\n\nUsing it inside the\n\n[`+page.svelte`](https://github.com/codebam/svelte-auth/blob/master/src/routes/%2Bpage.svelte)\n\nis as simple as this one line.\n\n```\nexport let data: {auth: boolean, song: {id: string, url: string}};\n```\n\n### Playlist Management\n\nNow I wrote more form actions for adding and removing songs from the playlist\n\n[here](https://github.com/codebam/svelte-auth/blob/master/src/routes/playlist/%2Bpage.server.ts).\n\n```typescript\nimport { redirect } from '@sveltejs/kit';\nimport tryLogin from '$lib/tryLogin';\n\nexport const actions = {\n\tadd: async (event) => {\n\t\tif (await tryLogin(event.cookies.get('session'), event.platform?.env?.DB)) {\n\t\t\tconst session = JSON.parse(event.cookies.get('session'));\n\t\t\tconst email = session.email;\n\t\t\tconst formdata = await event.request.formData();\n\t\t\tconst v = new URL(formdata.get('url')).searchParams.get('v');\n\t\t\tawait event.platform?.env?.DB.prepare('INSERT INTO Playlist VALUES (?, ?, ?, ?)')\n\t\t\t\t.bind(crypto.randomUUID(), email, v, Math.floor(new Date().getTime()))\n\t\t\t\t.all();\n\t\t}\n\t\tthrow redirect(303, '/');\n\t},\n\tremove: async (event) => {\n\t\tif (await tryLogin(event.cookies.get('session'), event.platform?.env?.DB)) {\n\t\t\tconst formdata = await event.request.formData();\n\t\t\tconst id = formdata.get('id');\n\t\t\tawait event.platform?.env?.DB.prepare('DELETE FROM Playlist WHERE id=?').bind(id).all();\n\t\t}\n\t\tthrow redirect(303, '/');\n\t}\n};\n```\n\n### User Interface\n\nNow this allows us to add songs, we just need the interface for it. So I wrote\n\nthe [main page](https://github.com/codebam/svelte-auth/blob/master/src/routes/%2Bpage.svelte)\n\nto allow logged in users to submit songs.\n\n```svelte\n<script lang=\"ts\">\n\timport { enhance } from '$app/forms';\n\timport YouTube from '$lib/svelte-youtube.svelte';\n\texport let data: { auth: boolean; song: { id: string; url: string } };\n</script>\n\n{#if !data.auth}\n\t<p>Visit <a href=\"/login\">login</a></p>\n{/if}\n\n<YouTube\n\ton:end={() => document.getElementById('remove_song')?.click()}\n\tvideoId={data.song.url}\n\toptions={{ playerVars: { autoplay: 1 } }}\n/>\n\n<form style=\"display: none;\" method=\"POST\" action=\"/playlist?/remove\" use:enhance>\n\t<input name=\"id\" value={data.song.id} />\n\t<button id=\"remove_song\">remove</button>\n</form>\n\n{#if data.auth}\n\t<form method=\"POST\" action=\"/playlist?/add\" use:enhance>\n\t\t<label\n\t\t\t>Submit a song\n\t\t\t<input name=\"url\" type=\"url\" placeholder=\"paste a youtube link\" pattern=\".*v=.*\" />\n\t\t</label>\n\t\t<button>Submit</button>\n\t</form>\n\t<p><a href=\"/logout\">Log out</a></p>\n{/if}\n```\n\n### Conclusion\n\nIf you have any problems or you want to see the entire repo, the code is\n\n[here](https://github.com/codebam/svelte-auth).\n"}