Readme

Authentication middleware

Guards your public http vals behind a login page.

c2a79825e9d89429014a036c29887c670806ee3f0188e01cde09adad193a6407.png

This val use a json web token stored as an http-only cookie to persist authentication.

Usage

Set an AUTH_SECRET_KEY env variable (used to sign/verify jwt tokens) to a random string.

Then use an API token to authenticate.

import { auth } from "https://esm.town/v/pomdtr/auth_middleware";

async function handler(req: Request): Promise<Response> {
  return new Response("You are authenticated!");
}

export default auth(handler);

See @pomdtr/test_auth for an example

⚠️ Make sure to only provides your api token to vals you trust (i.e. your own), as it gives access to your whole account.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import * as cookie from "https://deno.land/std/http/cookie.ts";
import { html } from "https://esm.town/v/stevekrouse/html?v=5";
import jwt from "npm:jsonwebtoken";
const loginPage = `<html>
<head>
<link rel="icon" href="https://fav.farm/🔒" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
</head>
<body>
<main class="container">
<article>
<form method="post">
<label for="token"><a href="https://www.val.town/settings/api">API Token</a></label>
<input id="token" name="token" type="password" />
<button type="submit">Submit</button>
</form>
</article>
</main>
</body>
</html>`;
export function redirect(location: string): Response {
return new Response(null, {
headers: {
location,
},
status: 302,
});
}
type User = {
id: string;
username: string;
};
async function fetchUser(token: string): Promise<User> {
const resp = await fetch("https://api.val.town/v1/me", {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (resp.status !== 200) {
throw new Error("Could not fetch user");
}
return resp.json();
}
async function isCurrentUser(userID: string) {
const currentUser = await fetchUser(Deno.env.get("valtown"));
return userID == currentUser.id;
}
function signout() {
const res = redirect("/");
cookie.setCookie(res.headers, {
name: "user-jwt",
value: "",
httpOnly: true,
secure: true,
sameSite: "None",
maxAge: 0,
});
return res;
}
export function auth(handler) {
const secretKey = Deno.env.get("AUTH_SECRET_KEY");
if (!secretKey) {
throw new Error("env var AUTH_SECRET_KEY not set");
}
return async (req: Request) => {
const { pathname, hostname, origin } = new URL(req.url);
if (pathname == "/signin" && req.method == "GET") {
return html(loginPage);
}
if (pathname == "/signin" && req.method == "POST") {
const formData = await req.formData();
const token = formData.get("token").toString();
const user = await fetchUser(token);
if (await isCurrentUser(user.id)) {
return new Response("Unauthorized", {
status: 403,
});
}
const webToken = jwt.sign(user, secretKey, { expiresIn: "7d" });
const res = redirect("/");
cookie.setCookie(res.headers, {
name: "user-jwt",
value: webToken,
httpOnly: true,
secure: true,
sameSite: "None",
});
return res;
👆 This is a val. Vals are TypeScript snippets of code, written in the browser and run on our servers. Create scheduled functions, email yourself, and persist small pieces of data — all from the browser.