-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
61 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,137 +7,98 @@ Use cookie-based sessions with the [Hono](https://hono.dev/) framework. Currentl | |
- Built-in Memory and Cookie storage drivers (more coming soon) | ||
- Encrypted cookies thanks to [iron-webcrypto](https://github.com/brc-dd/iron-webcrypto) | ||
- Session expiration after inactivity | ||
- Session key rotation, for mitigating session fixation attacks | ||
- Session key rotation* | ||
|
||
## Usage | ||
> *CookieStore is not able to rotate session keys by nature of how a pure cookie session works (no server-side state). | ||
### Cloudflare Workers | ||
## Installation and Usage | ||
|
||
Install from NPM | ||
``` | ||
npm install hono-sessions | ||
``` | ||
### Deno | ||
|
||
Here is a full-fledged example that shows what a login form might look like: | ||
Simply include the package from `deno.land/x` | ||
|
||
```ts | ||
import { Hono } from 'hono' | ||
import { sessionMiddleware, CookieStore, Session } from 'hono-sessions' | ||
import { sessionMiddleware } from 'https://deno.land/x/hono_sessions/mod.ts' | ||
``` | ||
|
||
const store = new CookieStore() | ||
### Bun, Cloudflare Workers | ||
|
||
Install the NPM package | ||
``` | ||
npm install hono-sessions | ||
``` | ||
|
||
const app = new Hono() | ||
## Examples | ||
|
||
const sessionRoutes = new Hono<{ | ||
### Deno | ||
```ts | ||
import { Hono } from 'https://deno.land/x/[email protected]/mod.ts' | ||
import { | ||
Session, | ||
sessionMiddleware, | ||
CookieStore | ||
} from 'https://deno.land/x/hono_sessions/mod.ts' | ||
|
||
const app = new Hono<{ | ||
Variables: { | ||
session: Session, | ||
session_key_rotation: boolean | ||
} | ||
}>() | ||
|
||
sessionRoutes.use('*', sessionMiddleware({ | ||
const store = new CookieStore() | ||
|
||
app.use('*', sessionMiddleware({ | ||
store, | ||
expireAfterSeconds: 900, // delete session after 15 minutes of inactivity | ||
encryptionKey: 'password_that_is_at_least_32_characters_long' // Required while using CookieStore. Please use a secure, un-guessable password! | ||
encryptionKey: 'password_at_least_32_characters_long', // Required for CookieStore, recommended for others | ||
expireAfterSeconds: 900, // Expire session after 15 minutes | ||
cookieOptions: { | ||
sameSite: 'Lax', | ||
}, | ||
})) | ||
|
||
sessionRoutes.post('/login', async (c) => { | ||
app.get('/', async (c, next) => { | ||
const session = c.get('session') | ||
|
||
const { email, password } = await c.req.parseBody() | ||
|
||
if (password === 'correct') { | ||
c.set('session_key_rotation', true) | ||
session.set('email', email) | ||
session.set('failed-login-attempts', null) | ||
session.flash('message', 'Login Successful') | ||
if (session.get('counter')) { | ||
session.set('counter', session.get('counter') as number + 1) | ||
} else { | ||
const failedLoginAttempts = (session.get('failed-login-attempts') || 0) as number | ||
session.set('failed-login-attempts', failedLoginAttempts + 1) | ||
session.flash('error', 'Incorrect username or password') | ||
session.set('counter', 1) | ||
} | ||
|
||
return c.redirect('/') | ||
}) | ||
|
||
sessionRoutes.post('/logout', (c) => { | ||
c.get('session').deleteSession() | ||
return c.redirect('/') | ||
}) | ||
|
||
sessionRoutes.get('/', (c) => { | ||
const session = c.get('session') | ||
|
||
const message = session.get('message') || '' | ||
const error = session.get('error') || '' | ||
const failedLoginAttempts = session.get('failed-login-attempts') | ||
const email = session.get('email') | ||
|
||
return c.html(`<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Hono Sessions</title> | ||
</head> | ||
<body> | ||
<p>${message}</p> | ||
<p>${error}</p> | ||
<p>${failedLoginAttempts ? `Failed login attempts: ${failedLoginAttempts}` : ''}</p> | ||
${email ? | ||
`<form id="logout" action="/logout" method="post"> | ||
<button name="logout" type="submit">Log out ${email}</button> | ||
</form>` | ||
: | ||
`<form id="login" action="/login" method="post"> | ||
<p> | ||
<input id="email" name="email" type="text" placeholder="[email protected]"> | ||
</p> | ||
<p> | ||
<input id="password" name="password" type="password" placeholder="password"> | ||
</p> | ||
<button name="login" type="submit">Log in</button> | ||
</form>` | ||
} | ||
</body> | ||
</html>`) | ||
return c.html(`<h1>You have visited this page ${ session.get('counter') } times</h1>`) | ||
}) | ||
|
||
app.route('/', sessionRoutes) | ||
|
||
export default app | ||
Deno.serve(app.fetch) | ||
``` | ||
|
||
### Deno | ||
|
||
There is a Deno package available on `deno.land/x`. | ||
### Bun | ||
|
||
```ts | ||
import { Hono } from 'https://deno.land/x/hono/mod.ts' | ||
import { sessionMiddleware, CookieStore, Session } from 'https://deno.land/x/hono_sessions/mod.ts' | ||
import { Hono } from 'hono' | ||
import { sessionMiddleware, CookieStore, Session } from 'hono-sessions' | ||
|
||
// Same as CF Workers, however instead of: | ||
// export default app | ||
// Same as Deno, however instead of: | ||
// Deno.serve(app.fetch) | ||
// use: | ||
|
||
Deno.serve(app.fetch) | ||
export default { | ||
port: 3000, | ||
fetch: app.fetch | ||
} | ||
``` | ||
|
||
### Bun | ||
### Cloudflare Workers | ||
|
||
```ts | ||
import { Hono } from 'hono' | ||
import { sessionMiddleware, CookieStore, Session } from 'hono-sessions' | ||
|
||
// Same as CF Workers, however instead of: | ||
// export default app | ||
// Same as Deno, however instead of: | ||
// Deno.serve(app.fetch) | ||
// use: | ||
|
||
export default { | ||
port: 3000, | ||
fetch: app.fetch | ||
} | ||
export default app | ||
``` | ||
|
||
## Contributing | ||
|
@@ -147,5 +108,13 @@ This package is built Deno-first, so you'll need to have Deno installed in your | |
Once Deno is installed, there is a test server you can run a basic web server to check your changes: | ||
|
||
``` | ||
deno run --allow-net --watch test/server_deno.ts | ||
deno run --allow-net --watch test/deno/server_deno.ts | ||
``` | ||
|
||
There's also a [Playwright](https://playwright.dev/) test suite. By default, it is set up to run a Deno server with the MemoryStore driver. In Github actions, it runs through a series of runtimes and storage drivers when a pull request is made. | ||
|
||
``` | ||
cd playwright | ||
npm install | ||
npx playwright test | ||
``` |