feat(examples): add cookie-jwt-auth

This commit is contained in:
Jamie Barton 2024-09-09 10:10:44 +01:00
parent 50e23ce53f
commit 6d39995257
8 changed files with 301 additions and 0 deletions

View File

@ -0,0 +1,26 @@
# Cookie JWT Auth Example
This example showcases how to uses a JWT to store information about the logged in user.
## Getting Started
1. Install dependencies:
```bash
composer install
```
2. Start the server:
```bash
composer start
```
3. Start the database server:
```bash
brew install tursodatabase/tap/turso
turso dev --port 8001
```
4. Visit the URL [http://localhost:8000](http://localhost:8000) in your browser.

View File

@ -0,0 +1,21 @@
{
"require": {
"notrab/dumbo": "@dev",
"latte/latte": "^3.0",
"darkterminal/turso-client-http": "^2.9",
"firebase/php-jwt": "^6.10"
},
"repositories": [
{
"type": "path",
"url": "../../"
}
],
"scripts": {
"start": [
"Composer\\Config::disableProcessTimeout",
"php -S localhost:8000 -t ."
]
},
"prefer-stable": false
}

View File

@ -0,0 +1,177 @@
<?php
require __DIR__ . "/vendor/autoload.php";
use Dumbo\Dumbo;
use Dumbo\Helpers\Cookie;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Latte\Engine as LatteEngine;
use Darkterminal\TursoHttp\LibSQL;
$app = new Dumbo();
$latte = new LatteEngine();
$dsn = "http://127.0.0.1:8001";
$db = new LibSQL($dsn);
$latte->setAutoRefresh(true);
$latte->setTempDirectory(null);
const JWT_SECRET = "your_jwt_secret_key";
const JWT_EXPIRATION = 3600; // 1 hour
const COOKIE_NAME = "jwt_session";
$db->execute("
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL
)
");
function render($latte, $view, $params = [])
{
return $latte->renderToString(__DIR__ . "/views/$view.latte", $params);
}
function createJWT($userId, $username)
{
$issuedAt = time();
$expirationTime = $issuedAt + JWT_EXPIRATION;
$payload = [
"iat" => $issuedAt,
"exp" => $expirationTime,
"userId" => $userId,
"username" => $username,
];
return JWT::encode($payload, JWT_SECRET, "HS256");
}
function verifyJWT($jwt)
{
try {
$decoded = JWT::decode($jwt, new Key(JWT_SECRET, "HS256"));
return (array) $decoded;
} catch (Exception $e) {
return null;
}
}
$app->use(function ($c, $next) {
$jwt = Cookie::getCookie($c, COOKIE_NAME);
if ($jwt) {
$payload = verifyJWT($jwt);
if ($payload) {
$c->set("user", $payload);
// Refresh JWT if it's close to expiration
if (time() > $payload["exp"] - 300) {
// Refresh if less than 5 minutes left
$newJwt = createJWT($payload["userId"], $payload["username"]);
Cookie::setCookie($c, COOKIE_NAME, $newJwt, [
"httpOnly" => true,
"secure" => true,
"path" => "/",
"maxAge" => JWT_EXPIRATION,
"sameSite" => Cookie::SAME_SITE_LAX,
]);
}
} else {
Cookie::deleteCookie($c, COOKIE_NAME);
}
}
return $next($c);
});
$app->get("/", function ($c) use ($latte) {
$user = $c->get("user");
$html = render($latte, "home", [
"user" => $user,
]);
return $c->html($html);
});
$app->get("/register", function ($c) use ($latte) {
$html = render($latte, "register");
return $c->html($html);
});
$app->post("/register", function ($c) use ($db, $latte) {
$body = $c->req->body();
$username = $body["username"] ?? "";
$password = $body["password"] ?? "";
if (empty($username) || empty($password)) {
$html = render($latte, "register", [
"error" => "Username and password are required",
]);
return $c->html($html);
}
try {
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
$db->prepare(
"INSERT INTO users (username, password) VALUES (?, ?)"
)->execute([$username, $hashedPassword]);
return $c->redirect("/login");
} catch (Exception $e) {
$html = render($latte, "register", [
"error" => "Username already exists",
]);
return $c->html($html);
}
});
$app->get("/login", function ($c) use ($latte) {
$html = render($latte, "login");
return $c->html($html);
});
$app->post("/login", function ($c) use ($db, $latte) {
$body = $c->req->body();
$username = $body["username"] ?? "";
$password = $body["password"] ?? "";
$result = $db
->query("SELECT * FROM users WHERE username = ?", [$username])
->fetchArray(LibSQL::LIBSQL_ASSOC);
if (!empty($result) && password_verify($password, $result[0]["password"])) {
$jwt = createJWT($result[0]["id"], $result[0]["username"]);
Cookie::setCookie($c, COOKIE_NAME, $jwt, [
"httpOnly" => true,
"secure" => true,
"path" => "/",
"maxAge" => JWT_EXPIRATION,
"sameSite" => Cookie::SAME_SITE_LAX,
]);
return $c->redirect("/");
} else {
$html = render($latte, "login", [
"error" => "Invalid username or password",
]);
return $c->html($html);
}
});
$app->get("/logout", function ($c) {
Cookie::deleteCookie($c, COOKIE_NAME);
return $c->redirect("/");
});
$app->get("/protected", function ($c) use ($latte) {
$user = $c->get("user");
if (!$user) {
return $c->redirect("/login");
}
$html = render($latte, "protected", [
"user" => $user,
]);
return $c->html($html);
});
$app->run();

View File

@ -0,0 +1,13 @@
{layout 'layout.latte'}
{block content}
<h1>Welcome to JWT Auth Example</h1>
{if $user}
<p>Hello, {$user['username']}!</p>
<a href="/protected">Protected Page</a> |
<a href="/logout">Logout</a>
{else}
<p>You are not logged in.</p>
<a href="/login">Login</a> | <a href="/register">Register</a>
{/if}
{/block}

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>Cookie Auth</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
{if isset($flash_message)}
<p style="color: green;">{$flash_message}</p>
{/if}
{include content}
</body>
</html>

View File

@ -0,0 +1,21 @@
{layout 'layout.latte'}
{block content}
<h1>Login</h1>
{if isset($error)}
<p style="color: red;">{$error}</p>
{/if}
<form action="/login" method="post">
<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit">Login</button>
</form>
<p>Don't have an account? <a href="/register">Register</a></p>
<p><a href="/">Back to Home</a></p>
{/block}

View File

@ -0,0 +1,8 @@
{layout 'layout.latte'}
{block content}
<h1>Protected Page</h1>
<p>Welcome, {$user['username']}! This is a protected page.</p>
<p>Your user ID is: {$user['userId']}</p>
<p><a href="/">Back to Home</a></p>
{/block}

View File

@ -0,0 +1,21 @@
{layout 'layout.latte'}
{block content}
<h1>Register</h1>
{if isset($error)}
<p style="color: red;">{$error}</p>
{/if}
<form action="/register" method="post">
<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit">Register</button>
</form>
<p>Already have an account? <a href="/login">Login</a></p>
<p><a href="/">Back to Home</a></p>
{/block}