Manage Authorization
97 |98 | This component is not aware of auth status. 99 |
100 |├── .eslintignore ├── .eslintrc.cjs ├── .gitattributes ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── docs ├── screenshot1.png ├── screenshot2.png └── screenshot3.png ├── package-lock.json ├── package.json ├── src ├── app.css ├── app.d.ts ├── app.html ├── lib │ └── AppwriteService.ts └── routes │ ├── +layout.svelte │ ├── +page.server.ts │ ├── +page.svelte │ ├── Card.svelte │ └── login │ └── +page.server.ts ├── static ├── cover.png ├── favicon.png ├── logo.svg ├── matej.webp └── matus.webp ├── svelte.config.js ├── tsconfig.json └── vite.config.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "pluginSearchDirs": ["."], 8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Matúš Ferčák 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |  2 | 3 | # Almost SSR - Svelte Kit 4 | 5 | > Demo application with authorized server-side and client-side rendering. 6 | 7 | ## 👀 Looking for Different Framework? 8 | 9 | * [🔦 Next.js](https://github.com/Meldiron/appwrite-ssr-next-js) | [next-js.ssr.almostapps.eu](https://next-js.ssr.almostapps.eu/) 10 | * [🔦 Nuxt](https://github.com/Meldiron/appwrite-ssr-nuxt) | [nuxt.ssr.almostapps.eu](https://nuxt.ssr.almostapps.eu/) 11 | * [🔦 Astro](https://github.com/Meldiron/appwrite-ssr-astro) | [astro.ssr.almostapps.eu](https://astro.ssr.almostapps.eu/) 12 | * [🔦 Qwik](https://github.com/Meldiron/appwrite-ssr-qwik) | [qwik.ssr.almostapps.eu](https://qwik.ssr.almostapps.eu/) 13 | * [🔦 Remix](https://github.com/Meldiron/appwrite-ssr-remix) | [remix.ssr.almostapps.eu](https://remix.ssr.almostapps.eu/) 14 | 15 | ## 💭 So How Does It Work? 16 | 17 | Appwrite uses 1st party secure cookies for authorization. For legacy reasons, there are two such cookies. They are both very similar, but one's name ends with `_legacy` and is configured a tiny bit differently. It's also possible to use a fallback cookie, but that is not secure for production, and we will not be using that. 18 | 19 | To ensure server-side rendering works, we need to set those cookies on our SSR server hostname instead of the Appwrite hostname. Let's say our Appwrite instance is on `cloud.appwrite.io`, and our app is on `myapp.com`. SSR server on domain `myapp.com` won't receive `appwrite.io` cookies. This is expected behavior, as browsers keep 1st party cookies securely scoped to specific domains. 20 | 21 | To set those cookies on the SSR server, we need a special API endpoint in our SSR server. This endpoint will send a request to create a session, proxying email/password or other credentials. This endpoint next parses the response `set-cookie` header, replaces domain configuration on the cookies, and set's it's own `set-cookie` on the response to the client. 22 | 23 | When a client calls this endpoint, the cookie will now be set on the SSR server hostname instead of the Appwrite hostname. 24 | 25 | This makes server-side rendering work, but now client-side rendering is broken. Since `set-cookie` coming to the browser only includes a cookie for the SSR server, talking to the Appwrite server directly won't have a proper cookie - there is no auth cookie on the Appwrite hostname. To overcome this problem, we ensure the Appwrite hostname is a subdomain of the SSR hostname. For example, if our SSR server runs on `myapp.com`, Appwrite needs a custom domain configured on `appwrite.myapp.com`. With this setup, all requests to the Appwrite server will include auth cookies, and the user will be properly authorized. This is possible thanks to Appwrite prefixing the cookie domain with `.`, meaning all subdomains can also access the cookie. 26 | 27 | ## 🧰 Tech Stack 28 | 29 | - [Appwrite](https://appwrite.io/) 30 | - [Svelte Kit](https://kit.svelte.dev/) 31 | - [Pink Design](https://pink.appwrite.io/) 32 | - [TypeScript](https://www.typescriptlang.org/) 33 | 34 | ## 🛠️ Setup Server 35 | 36 | 1. Setup Appwrite server 37 | 2. Create project `almostSsr` 38 | 39 | ## 👀 Setup Client 40 | 41 | 1. Install libarries `npm install` 42 | 2. Update `AppwriteEndpoint` in `src/lib/AppwriteService.ts` 43 | 3. Start server `npm run dev` 44 | 45 | ## 🚀 Deployment 46 | 47 | 1. Deploy the frontend on your production domain. For example, `myapp.com`. 48 | 2. Add the frontend domain as a trusted platform in your Appwrite project. 49 | 3. Add a custom domain to your Appwrite project, which is a subdomain of your frontend. For example, `appwrite.myapp.com`. 50 | 4. Update `SsrHostname` and `AppwriteHostname` in `src/lib/AppwriteService.ts` with proper domains. 51 | 52 | ## 🤝 Contributing 53 | 54 | To contribute to frontend, make sure to use the [Pink Design](https://pink.appwrite.io/) design system. Ensure both dark and light theme work properly, as well as responsivity on mobile, tablet and desktop. 55 | 56 | When contributing with static files, ensure all images are in WEBP or SVG format. 57 | 58 | ## 🖼️ Screenshots 59 | 60 |  61 |  62 |  63 | 64 | ## 🤖 Auto-generated documentation 65 | 66 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). 67 | 68 | ## Creating a project 69 | 70 | If you're seeing this, you've probably already done this step. Congrats! 71 | 72 | ```bash 73 | # create a new project in the current directory 74 | npm create svelte@latest 75 | 76 | # create a new project in my-app 77 | npm create svelte@latest my-app 78 | ``` 79 | 80 | ## Developing 81 | 82 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 83 | 84 | ```bash 85 | npm run dev 86 | 87 | # or start the server and open the app in a new browser tab 88 | npm run dev -- --open 89 | ``` 90 | 91 | ## Building 92 | 93 | To create a production version of your app: 94 | 95 | ```bash 96 | npm run build 97 | ``` 98 | 99 | You can preview the production build with `npm run preview`. 100 | 101 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. 102 | -------------------------------------------------------------------------------- /docs/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meldiron/appwrite-ssr-svelte-kit/efde3d0cd9c3b52d5233bce27a20e3346f15aead/docs/screenshot1.png -------------------------------------------------------------------------------- /docs/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meldiron/appwrite-ssr-svelte-kit/efde3d0cd9c3b52d5233bce27a20e3346f15aead/docs/screenshot2.png -------------------------------------------------------------------------------- /docs/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meldiron/appwrite-ssr-svelte-kit/efde3d0cd9c3b52d5233bce27a20e3346f15aead/docs/screenshot3.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "appwrite-ssr-svelte-kit", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 11 | "lint": "prettier --plugin-search-dir . --check . && eslint .", 12 | "format": "prettier --plugin-search-dir . --write ." 13 | }, 14 | "devDependencies": { 15 | "@sveltejs/adapter-auto": "^2.0.0", 16 | "@sveltejs/kit": "^1.5.0", 17 | "@types/set-cookie-parser": "^2.4.2", 18 | "@typescript-eslint/eslint-plugin": "^5.45.0", 19 | "@typescript-eslint/parser": "^5.45.0", 20 | "eslint": "^8.28.0", 21 | "eslint-config-prettier": "^8.5.0", 22 | "eslint-plugin-svelte3": "^4.0.0", 23 | "prettier": "^2.8.0", 24 | "prettier-plugin-svelte": "^2.8.1", 25 | "sass": "^1.62.0", 26 | "svelte": "^3.54.0", 27 | "svelte-check": "^3.0.1", 28 | "tslib": "^2.4.1", 29 | "typescript": "^5.0.0", 30 | "vite": "^4.2.0" 31 | }, 32 | "type": "module", 33 | "dependencies": { 34 | "@appwrite.io/pink": "^0.0.6-rc.10", 35 | "appwrite": "^11.0.0", 36 | "set-cookie-parser": "^2.6.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | .gradient-border { 2 | border-radius: var(--card-border-radius, var(--border-radius-medium)); 3 | padding: 0.0625rem; 4 | background-color: hsl(var(--color-border)); 5 | background: linear-gradient( 6 | 180deg, 7 | hsl(var(--color-border)) 0%, 8 | hsl(var(--p-body-bg-color)) 100% 9 | ); 10 | } 11 | 12 | .theme-dark .card-is-failed { 13 | box-shadow: 0 1px 1px hsl(var(--color-danger-200)), 0 0 0 hsl(var(--color-danger-200)), 14 | inset 0 0.5px 0 0.5px hsl(var(--color-danger-120)), 15 | inset 0 -3px 0 -2px hsl(var(--color-danger-120)), 0 0 15px hsl(var(--color-danger-200)) !important; 16 | 17 | border: none !important; 18 | } 19 | 20 | .card-is-failed { 21 | box-shadow: 0 1px 1px hsl(var(--color-danger-50)), 0 0 0 hsl(var(--color-danger-50)), 22 | inset 0 0.5px 0 0.5px hsl(var(--color-danger-100)), 23 | inset 0 -3px 0 -2px hsl(var(--color-danger-100)), 0 0 15px hsl(var(--color-danger-50)) !important; 24 | 25 | border: none !important; 26 | } 27 | 28 | .theme-dark .card-is-pending { 29 | box-shadow: 0 1px 1px hsl(var(--color-warning-200)), 0 0 0 hsl(var(--color-warning-200)), 30 | inset 0 0.5px 0 0.5px hsl(var(--color-warning-120)), 31 | inset 0 -3px 0 -2px hsl(var(--color-warning-120)), 0 0 15px hsl(var(--color-warning-200)) !important; 32 | 33 | border: none !important; 34 | } 35 | 36 | .card-is-pending { 37 | box-shadow: 0 1px 1px hsl(var(--color-warning-50)), 0 0 0 hsl(var(--color-warning-50)), 38 | inset 0 0.5px 0 0.5px hsl(var(--color-warning-100)), 39 | inset 0 -3px 0 -2px hsl(var(--color-warning-100)), 0 0 15px hsl(var(--color-warning-50)) !important; 40 | 41 | border: none !important; 42 | } 43 | 44 | .theme-dark .card-is-complete { 45 | box-shadow: 0 1px 1px hsl(var(--color-success-200)), 0 0 0 hsl(var(--color-success-200)), 46 | inset 0 0.5px 0 0.5px hsl(var(--color-success-120)), 47 | inset 0 -3px 0 -2px hsl(var(--color-success-120)), 0 0 15px hsl(var(--color-success-200)) !important; 48 | 49 | border: none !important; 50 | } 51 | 52 | .card-is-complete { 53 | box-shadow: 0 1px 1px hsl(var(--color-success-50)), 0 0 0 hsl(var(--color-success-50)), 54 | inset 0 0.5px 0 0.5px hsl(var(--color-success-100)), 55 | inset 0 -3px 0 -2px hsl(var(--color-success-100)), 0 0 15px hsl(var(--color-success-50)) !important; 56 | 57 | border: none !important; 58 | } 59 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface Platform {} 9 | } 10 | } 11 | 12 | export {}; 13 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
62 | 64 | This is demo application. Use button below to create account. Notice both server-side 65 | rendering, and client-side requests are authorized. The whole process uses 1st party secure 66 | cookies. 67 |
68 |98 | This component is not aware of auth status. 99 |
100 |{account.$id}
50 | {/if}
51 |