├── dev.ts ├── deno.json ├── main.ts ├── README.md ├── components └── Button.tsx ├── routes ├── logout.ts ├── secret_or_redirect.tsx ├── secret_or_custom.tsx ├── api │ └── login.ts └── index.tsx ├── import_map.json └── fresh.gen.ts /dev.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S deno run -A --watch=static/,routes/ 2 | 3 | import dev from "$fresh/dev.ts"; 4 | 5 | await dev(import.meta.url, "./main.ts"); 6 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "start": "deno run -A --watch=static/,routes/ dev.ts" 4 | }, 5 | "importMap": "./import_map.json", 6 | "compilerOptions": { 7 | "jsx": "react-jsx", 8 | "jsxImportSource": "preact" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | 7 | import { start } from "$fresh/server.ts"; 8 | import manifest from "./fresh.gen.ts"; 9 | 10 | await start(manifest); 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fresh Auth Example 2 | 3 | This is the example repo that accompanied the tutorial, 4 | [How to Setup Auth with Fresh](https://deno.com/blog/setup-auth-with-fresh). 5 | 6 | ## Usage 7 | 8 | Start the project: 9 | 10 | ``` 11 | deno task start 12 | ``` 13 | 14 | This will watch the project directory and restart as necessary. 15 | -------------------------------------------------------------------------------- /components/Button.tsx: -------------------------------------------------------------------------------- 1 | import { JSX } from "preact"; 2 | import { IS_BROWSER } from "$fresh/runtime.ts"; 3 | 4 | export function Button(props: JSX.HTMLAttributes) { 5 | return ( 6 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /routes/logout.ts: -------------------------------------------------------------------------------- 1 | import { Handlers } from "$fresh/server.ts"; 2 | import { deleteCookie } from "std/http/cookie.ts"; 3 | 4 | export const handler: Handlers = { 5 | GET(req) { 6 | const url = new URL(req.url); 7 | const headers = new Headers(req.headers); 8 | deleteCookie(headers, "auth", { path: "/", domain: url.hostname }); 9 | 10 | headers.set("location", "/"); 11 | return new Response(null, { 12 | status: 302, 13 | headers, 14 | }); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /import_map.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "$fresh/": "https://deno.land/x/fresh@1.1.2/", 4 | "preact": "https://esm.sh/preact@10.11.0", 5 | "preact/": "https://esm.sh/preact@10.11.0/", 6 | "preact-render-to-string": "https://esm.sh/*preact-render-to-string@5.2.4", 7 | "@preact/signals": "https://esm.sh/*@preact/signals@1.0.3", 8 | "@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.0.1", 9 | "twind": "https://esm.sh/twind@0.16.17", 10 | "twind/": "https://esm.sh/twind@0.16.17/", 11 | "std/": "https://deno.land/std@0.160.0/" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /routes/secret_or_redirect.tsx: -------------------------------------------------------------------------------- 1 | import type { Handlers } from "$fresh/server.ts"; 2 | import { getCookies } from "std/http/cookie.ts"; 3 | 4 | export default function Home() { 5 | return ( 6 | 7 | Here is some secret 8 | 9 | ); 10 | } 11 | 12 | export const handler: Handlers = { 13 | GET(req, ctx) { 14 | const cookies = getCookies(req.headers); 15 | if (cookies.auth === "bar") { 16 | return ctx.render!(); 17 | } else { 18 | const url = new URL(req.url); 19 | url.pathname = "/"; 20 | return Response.redirect(url); 21 | } 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /routes/secret_or_custom.tsx: -------------------------------------------------------------------------------- 1 | import type { Handlers, PageProps } from "$fresh/server.ts"; 2 | import { getCookies } from "std/http/cookie.ts"; 3 | 4 | interface Data { 5 | isAllowed: boolean; 6 | } 7 | 8 | export default function Home({ data }: PageProps) { 9 | return ( 10 | 11 | {data.isAllowed ? "Here is some secret" : "You are not allowed here"} 12 | 13 | ); 14 | } 15 | 16 | export const handler: Handlers = { 17 | GET(req, ctx) { 18 | const cookies = getCookies(req.headers); 19 | 20 | return ctx.render!({ isAllowed: cookies.auth === "bar" }); 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /fresh.gen.ts: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This file is generated by fresh. 2 | // This file SHOULD be checked into source version control. 3 | // This file is automatically updated during development when running `dev.ts`. 4 | 5 | import config from "./deno.json" assert { type: "json" }; 6 | import * as $0 from "./routes/api/login.ts"; 7 | import * as $1 from "./routes/index.tsx"; 8 | import * as $2 from "./routes/logout.ts"; 9 | import * as $3 from "./routes/secret_or_custom.tsx"; 10 | import * as $4 from "./routes/secret_or_redirect.tsx"; 11 | 12 | const manifest = { 13 | routes: { 14 | "./routes/api/login.ts": $0, 15 | "./routes/index.tsx": $1, 16 | "./routes/logout.ts": $2, 17 | "./routes/secret_or_custom.tsx": $3, 18 | "./routes/secret_or_redirect.tsx": $4, 19 | }, 20 | islands: {}, 21 | baseUrl: import.meta.url, 22 | config, 23 | }; 24 | 25 | export default manifest; 26 | -------------------------------------------------------------------------------- /routes/api/login.ts: -------------------------------------------------------------------------------- 1 | import { Handlers } from "$fresh/server.ts"; 2 | import { setCookie } from "std/http/cookie.ts"; 3 | 4 | export const handler: Handlers = { 5 | async POST(req) { 6 | const url = new URL(req.url); 7 | const form = await req.formData(); 8 | if (form.get("username") === "deno" && form.get("password") === "land") { 9 | const headers = new Headers(); 10 | setCookie(headers, { 11 | name: "auth", 12 | value: "bar", // this should be a unique value for each session 13 | maxAge: 120, 14 | sameSite: "Lax", // this is important to prevent CSRF attacks 15 | domain: url.hostname, 16 | path: "/", 17 | secure: true, 18 | }); 19 | 20 | headers.set("location", "/"); 21 | 22 | return new Response(null, { 23 | status: 303, // See Other 24 | headers, 25 | }); 26 | } else { 27 | return new Response(null, { 28 | status: 403, 29 | }); 30 | } 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /routes/index.tsx: -------------------------------------------------------------------------------- 1 | import type { Handlers, PageProps } from "$fresh/server.ts"; 2 | import { getCookies } from "std/http/cookie.ts"; 3 | 4 | interface Data { 5 | isAllowed: boolean; 6 | } 7 | 8 | export default function Home({ data }: PageProps) { 9 | return ( 10 | 11 | secret or custom 12 | 13 | secret or redirect 14 | 15 | You currently {data.isAllowed ? "are" : "are not"} logged in. 16 | 17 | {!data.isAllowed ? : Logout} 18 | 19 | ); 20 | } 21 | 22 | function Login() { 23 | return ( 24 | 25 | 26 | 27 | Submit 28 | 29 | ); 30 | } 31 | 32 | export const handler: Handlers = { 33 | GET(req, ctx) { 34 | const cookies = getCookies(req.headers); 35 | 36 | return ctx.render!({ isAllowed: cookies.auth === "bar" }); 37 | }, 38 | }; 39 | --------------------------------------------------------------------------------