{"id":25902695,"url":"https://github.com/scriptraccoon/sveltekit-password","last_synced_at":"2026-03-04T07:05:38.303Z","repository":{"id":128642623,"uuid":"611041927","full_name":"ScriptRaccoon/sveltekit-password","owner":"ScriptRaccoon","description":"How to protect a SvelteKit page with a password","archived":false,"fork":false,"pushed_at":"2024-01-26T00:07:04.000Z","size":73,"stargazers_count":16,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-29T05:31:43.974Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://sveltekit-password.netlify.app/","language":"Svelte","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ScriptRaccoon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-03-08T01:31:17.000Z","updated_at":"2025-04-16T13:58:41.000Z","dependencies_parsed_at":"2024-01-26T01:26:18.292Z","dependency_job_id":"16169498-946d-441f-8d1b-be7d31aab07e","html_url":"https://github.com/ScriptRaccoon/sveltekit-password","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ScriptRaccoon/sveltekit-password","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ScriptRaccoon%2Fsveltekit-password","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ScriptRaccoon%2Fsveltekit-password/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ScriptRaccoon%2Fsveltekit-password/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ScriptRaccoon%2Fsveltekit-password/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ScriptRaccoon","download_url":"https://codeload.github.com/ScriptRaccoon/sveltekit-password/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ScriptRaccoon%2Fsveltekit-password/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30075425,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-04T05:31:57.858Z","status":"ssl_error","status_checked_at":"2026-03-04T05:31:38.462Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-03-03T03:17:10.505Z","updated_at":"2026-03-04T07:05:38.281Z","avatar_url":"https://github.com/ScriptRaccoon.png","language":"Svelte","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SvelteKit-Password\n\nhttps://sveltekit-password.netlify.app/\n\nThis repository demonstrates how to implement a password-protected page inside of a SvelteKit application. Below you also find a step-by-step tutorial. We will also see how to protect multiple pages.\n\nThe password is: sveltekit2023\n\n# How it's done\n\n## Setup\n\nFirst of all, we start with three simple pages: `/`, `/blog` and `/personal`.\n\n```\n/routes\n\n  +page.svelte\n\n  /blog\n    +page.svelte\n\n  /personal\n    +page.svelte\n```\n\nWe would like to protect `/personal` with a password.\n\nLet's first create a password and save it in our `.env` file:\n\n```\nSECRET_PASSWORD = sveltekit123\n```\n\n## Login page\n\nNow let's create a login page `/login/+page.svelte` with a login form:\n\n```html\n\u003ch1\u003eLogin\u003c/h1\u003e\n\n\u003cform method=\"POST\"\u003e\n\t\u003clabel\u003ePassword\u003cinput name=\"password\" type=\"password\" /\u003e\u003c/label\u003e\n\t\u003cbutton\u003eLogin\u003c/button\u003e\n\u003c/form\u003e\n```\n\nTo handle the POST request, we create `/login/+page.server.ts` and add an Action handler which validates the password:\n\n```typescript\nimport type { Actions } from \"./$types\";\nimport { SECRET_PASSWORD } from \"$env/static/private\";\nimport { fail, redirect } from \"@sveltejs/kit\";\nimport { save_session } from \"../../db/session\";\n\nexport const actions: Actions = {\n  default: async ({ request, cookies }) =\u003e {\n    const data = await request.formData();\n    const password = data.get(\"password\");\n    const password_correct = password === SECRET_PASSWORD;\n\n    if (password_correct) {\n      ...\n    }\n\n    return fail(401, { password_correct });\n  },\n};\n```\n\nIf the password is not correct, we send the info back to the login page. This info is contained in the `form` object:\n\n```svelte\n\u003cscript lang=\"ts\"\u003e\n  import type { ActionData } from \"./$types\";\n  export let form: ActionData;\n\u003c/script\u003e\n\n// ... login form ...\n\n{#if form \u0026\u0026 !form.password_correct}\n  \u003cp\u003eThe password is not correct.\u003c/p\u003e\n{/if}\n```\n\n## Cookies\n\nHowever, if the password is correct, our action handler sets a cookie and then redirects to the personal page:\n\n```typescript\nif (password_correct) {\n\tconst session_id = save_session();\n\tconst one_week = 60 * 60 * 24 * 7;\n\tcookies.set(\"session_id\", session_id, {\n\t\tpath: \"/\",\n\t\tmaxAge: one_week,\n\t});\n\n\tthrow redirect(303, \"/personal\");\n}\n```\n\nThe cookie lasts for one week and is httpOnly by default. To generate it, we have used a utility function `save_session` from `session.ts` which basically implements a database in memory with the stored sessions.\n\n```typescript\nconst sessions = new Set(); // should be in a database\n\nexport function save_session(): string {\n\tconst session_id = crypto.randomUUID();\n\tsessions.add(session_id);\n\treturn session_id;\n}\n```\n\nThe utility `has_session` simply checks if the session is in the database.\n\n```typescript\nexport function has_session(session_id: string): boolean {\n\treturn sessions.has(session_id);\n}\n```\n\n## Password protection\n\nNow we need to add a load function to the personal page to check if the user has already logged in - using the cookie. If not, we redirect to the login page.\n\n```typescript\nimport type { PageServerLoad } from \"./$types\";\nimport { redirect } from \"@sveltejs/kit\";\nimport { has_session } from \"../../db/session\";\n\nexport const load: PageServerLoad = async ({ cookies }) =\u003e {\n\tconst session_id = cookies.get(\"session_id\");\n\tif (!session_id) throw redirect(307, \"/login\");\n\tconst logged_in = has_session(session_id);\n\tif (!logged_in) throw redirect(307, \"/login\");\n};\n```\n\nAnd that's it!\n\n## Progressive Enhancement\n\nWe can improve the UX of the login process by replacing the server-side navigation with a client-side navigation. This can be done simply by adding the action directive `use:enhance` to our login form:\n\n```svelte\n\u003cscript lang=\"ts\"\u003e\n  import { enhance } from \"$app/forms\";\n  // ...\n\u003c/script\u003e\n\n\u003ch1\u003eLogin\u003c/h1\u003e\n\n\u003cform method=\"POST\" use:enhance\u003e\n  // ..\n\u003c/form\u003e\n```\n\nNow, when JS is enabled, the redirection will look much smoother.\n\n## Protect more pages\n\nSo far, we have only protected one single page inside our application. If you want to protect multiple pages, you can either use hooks (see the [documentation](https://kit.svelte.dev/docs/hooks) or the video [Protect SvelteKit Routes with Hooks](https://www.youtube.com/watch?v=K1Tya6ovVOI) by Huntabyte) or use the following method:\n\nLet's create a nested page inside of our personal page: `/personal/notes/+page.svelte`. Its content is not relevant for now, but you might want to add a heading to identify it. With our current solution, you can access it even when you are not logged in. Of course we could just copy-paste our code from `+page.server.ts`, but this is not a good way. Instead, we move the login logic to a layout load function as follows.\n\nCreate an empty layout at `/personal/+layout.svelte`.\n\n```svelte\n\u003cslot /\u003e\n```\n\nThis is a _nested layout_ which is added to our root layout. It does not replace it.\n\nCreate its corresponding server file `personal/+layout.server.ts` and move the login logic there, thereby also replacing the type `PageServerLoad` by `LayoutServerLoad`.\n\n```typescript\nimport type { LayoutServerLoad } from \"./$types\";\nimport { redirect } from \"@sveltejs/kit\";\nimport { has_session } from \"../../db/session\";\n\nexport const load: LayoutServerLoad = async ({ cookies }) =\u003e {\n\tconst session_id = cookies.get(\"session_id\");\n\tif (!session_id) throw redirect(307, \"/login\");\n\tconst logged_in = has_session(session_id);\n\tif (!logged_in) throw redirect(307, \"/login\");\n};\n```\n\nThus, our `personal/+page.server.ts` only keeps the empty action:\n\n```typescript\nimport type { Actions } from \"./$types\";\n\nexport const actions: Actions = {\n\tdefault: async () =\u003e {},\n};\n```\n\nWhat we have done protects all nested pages inside of the `/personal` folder (since they load the nested layout), and this applies in particular to our `/personal/notes` page.\n\nThere is a security issue, however, as explained by Hunterbyte in the video [Are your routes actually protected?](https://www.youtube.com/watch?v=UbhhJWV3bmI). Navigate to `/personal`, delete the cookie (imagine that the cookie is expired), and try to go to `/personal/notes`. You have access even though you should not be logged in anymore. In other words, even though our solution protects the pages from users who are not logged in at all, it does not proctect the pages from users who have just been logged out (in the same session).\n\nThe reason is that the server load of `/personal/notes` (which is empty right now, we did not create it) does not load the server load inside of `personal/+layout.server.ts`. You can check this by console logs. Fortunately, there is a way to solve this: we create `/personal/notes/+page.server.ts` and add the following:\n\n```typescript\nimport type { PageServerLoad } from \"./$types\";\n\nexport const load: PageServerLoad = async ({ parent }) =\u003e {\n\tawait parent();\n};\n```\n\nWe also add this code to `/personal/+page.server.ts`. The `parent` function refers to the surrounding layout server load. This way we force it to run again and hence check if the user is still logged in.\n\nAs you see this grew a bit out of hand. You might not need to do this when you just want to protect a single page, and maybe you also do not really care about logging out users properly who already have the password. But for bigger endeavors (such as an admin page) it becomes apparent that a layout (or page) server load function is not ideal. Hooks are a better solution. Again, check out the video [Protect SvelteKit Routes with Hooks](https://www.youtube.com/watch?v=K1Tya6ovVOI) by Huntabyte. Maybe I will create a \"follow-up\" repository to this one which uses hooks instead.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscriptraccoon%2Fsveltekit-password","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscriptraccoon%2Fsveltekit-password","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscriptraccoon%2Fsveltekit-password/lists"}