Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add server side session storage #265

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 146 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,22 @@ export default defineNitroPlugin(() => {
})
```

### Extending Cookie Lifetime

When using cookie mode, only the session data itself can be enriched through the session hooks. If you want to extend the lifetime of the cookie itself, you could use a middleware that refreshes the cookie by re-setting the session data. (At this point you can also add any other custom data to your cookies)

Example middleware:

```ts
// server/middleware/cookie-lifetime-extend.ts
export default defineEventHandler((event) => {
atinux marked this conversation as resolved.
Show resolved Hide resolved
const session = await getUserSession(event)
if (session && Object.keys(session).length > 0) {
await setUserSession(event, session)
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

) is missing at the end here ;)

```

## Server-Side Rendering

You can make authenticated requests both from the client and the server. However, you must use `useRequestFetch()` to make authenticated requests during SSR if you are not using `useFetch()`
Expand Down Expand Up @@ -579,42 +595,158 @@ If you are caching your routes with `routeRules`, please make sure to use [Nitro

## Configuration

We leverage `runtimeConfig.session` to give the defaults option to [h3 `useSession`](https://h3.unjs.io/examples/handle-session).
### Session Storage

You can overwrite the options in your `nuxt.config.ts`:
Nuxt Auth Utils supports different session storage modes that can be configured in your `nuxt.config.ts`:

```ts
export default defineNuxtConfig({
modules: ['nuxt-auth-utils'],
auth: {
storageType: 'cookie', // 'memory', 'cache', 'nuxt-session'
}
})
```

#### Storage Types

- **`cookie`** (default): Stores session data in encrypted cookies. This is the most secure option and works well for most use cases.

```ts
auth: {
storageType: 'cookie'
}
```

- **`cache`**: Uses Nitro's cache storage. Useful when you need to store larger session data that might exceed cookie size limits.

```ts
auth: {
storageType: 'cache'
}
```

- **`memory`**: Stores sessions in memory. Only suitable for development or testing.

```ts
auth: {
storageType: 'memory'
}
```

> [!WARNING]
> Memory storage is cleared when the server restarts and doesn't work with multiple server instances. Not recommended for production use.

- **`nuxt-session`**: Uses a custom storage mount named 'nuxt-session'. Useful when you want to use a different storage driver.

```ts
// nuxt.config.ts
export default defineNuxtConfig({
auth: {
storageType: 'nuxt-session'
},
nitro: {
storage: {
'nuxt-session': {
driver: 'fsLite',
base: './.data/sessions'
}
}
}
})
```

> [!NOTE]
> This will store sessions in the `.data/sessions` directory. Make sure to add `.data` to your `.gitignore`.

#### Session Configuration

You can configure session behavior through the `auth` or `runtimeConfig` options:

```ts
export default defineNuxtConfig({
auth: {
storageType: 'cookie'
},
runtimeConfig: {
session: {
maxAge: 60 * 60 * 24 * 7 // 1 week
name: 'nuxt-session', // Cookie name
maxAge: 60 * 60 * 24 * 7, // 1 week
password: process.env.NUXT_SESSION_PASSWORD,
cookie: {
sameSite: 'lax',
// Additional cookie options
// secure: true,
// domain: 'example.com',
// path: '/'
}
}
}
})
```

Our defaults are:
We leverage `runtimeConfig.session` to give the defaults option to [h3 `useSession`](https://h3.unjs.io/examples/handle-session).
Checkout the [`SessionConfig`](https://github.com/unjs/h3/blob/c04c458810e34eb15c1647e1369e7d7ef19f567d/src/utils/session.ts#L20) for all options.

> [!NOTE]
> When using non-cookie storage types, the cookie only contains a session ID while the actual session data is stored in the selected storage.

When using a non-cookie mode

```ts
{
name: 'nuxt-session',
password: process.env.NUXT_SESSION_PASSWORD || '',
cookie: {
sameSite: 'lax'
export default defineNuxtConfig({
auth: {
storageType: 'cache',
sessionInactivityMaxAge: 60 * 60 * 24 * 30, // Session timeout after inactivity (30 days)
autoExtendSession: true // Extend session on each request
},
runtimeConfig: {
session: {
password: process.env.NUXT_SESSION_PASSWORD,
}
}
}
})
```

> [!IMPORTANT]
> The `sessionInactivityMaxAge` option is specifically designed for non-cookie storage types to manage and cleanup inactive sessions. When using this configuration, cookies still respect the `maxAge` setting from the session configuration, if one is specified. Whether you need both `maxAge` and `sessionInactivityMaxAge` will depend on your specific application requirements and session management strategy.

## Session Cleanup

When using non-cookie storage types, you may want to clean up expired sessions periodically. This can be done using Nitro's scheduled tasks feature.

1. Create a task file:

```ts:server/tasks/clear-sessions.ts
export default defineTask({
meta: {
name: 'clear-sessions',
description: 'Clear expired sessions',
},
run({ payload, context }) {
console.log('Running clear-sessions task...')
cleanupOrphanedUserSessions()
return { result: 'Success' }
},
})
```

You can also overwrite the session config by passing it as 3rd argument of the `setUserSession` and `replaceUserSession` functions:
2. Configure the task schedule in your `nuxt.config.ts`:

```ts
await setUserSession(event, { ... } , {
maxAge: 60 * 60 * 24 * 7 // 1 week
export default defineNuxtConfig({
nitro: {
experimental: {
tasks: true
},
scheduledTasks: {
'*/5 * * * *': ['clear-sessions'] // Run every 5 minutes
}
}
})
```

Checkout the [`SessionConfig`](https://github.com/unjs/h3/blob/c04c458810e34eb15c1647e1369e7d7ef19f567d/src/utils/session.ts#L20) for all options.
This will automatically clean up any expired sessions based on your `sessionInactivityMaxAge` configuration.

## More

Expand Down
12 changes: 12 additions & 0 deletions playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,21 @@ export default defineNuxtConfig({
nitro: {
experimental: {
database: true,
// tasks: true,
},
// scheduledTasks: {
// '*/1 * * * *': ['clear-sessions'], // every minute clear overdue sessions
// },
},
auth: {
webAuthn: true,
// storageType: 'cache',
// sessionInactivityMaxAge: 60 * 2, // 2 minutes
// autoExtendSession: true,
},
// runtimeConfig: {
// session: {
// maxAge: 60 * 60 * 24 * 7, // 7 days
// },
// },
})
11 changes: 11 additions & 0 deletions playground/server/tasks/clear-sessions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default defineTask({
meta: {
name: 'clear-sessions',
description: 'Clear expired sessions',
},
run({ payload, context }) {

Check failure on line 6 in playground/server/tasks/clear-sessions.ts

View workflow job for this annotation

GitHub Actions / lint

'payload' is defined but never used. Allowed unused args must match /^_/u

Check failure on line 6 in playground/server/tasks/clear-sessions.ts

View workflow job for this annotation

GitHub Actions / lint

'context' is defined but never used. Allowed unused args must match /^_/u
console.log('Running clear-sessions task...')
cleanupOrphanedUserSessions()
return { result: 'Success' }
},
})
33 changes: 33 additions & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,25 @@
* @default false
*/
webAuthn?: boolean
/**
* Use session storage
* Use 'cache' for standard cache storage,
* 'cookie' for cookies,
* 'memory' for in-memory storage,
* 'nuxt-session' for a custom storage mount named 'nuxt-session'
* @default 'cache'
*/
storageType?: 'cache' | 'cookie' | 'memory' | 'nuxt-session'
/**
* Session inactivity max age in milliseconds
* @default 2592000000 (30 days)
*/
sessionInactivityMaxAge?: number
/**
* Auto extend session
* @default true
*/
autoExtendSession?: boolean
/**
* Hash options used for password hashing
*/
Expand Down Expand Up @@ -49,6 +68,9 @@
// Default configuration options of the Nuxt module
defaults: {
webAuthn: false,
storageType: 'cookie',
sessionInactivityMaxAge: 2592000, // 30 days
autoExtendSession: true,
hash: {
scrypt: {},
},
Expand Down Expand Up @@ -143,6 +165,17 @@
authenticate: {},
})

runtimeConfig.useSessionStorageType = runtimeConfig.useSessionStorageType || options.storageType

Check failure on line 168 in src/module.ts

View workflow job for this annotation

GitHub Actions / test

Type 'string | undefined' is not assignable to type 'string'.
runtimeConfig.sessionInactivityMaxAge = runtimeConfig.sessionInactivityMaxAge || options.sessionInactivityMaxAge

Check failure on line 169 in src/module.ts

View workflow job for this annotation

GitHub Actions / test

Type 'number | undefined' is not assignable to type 'number'.
runtimeConfig.autoExtendSession = runtimeConfig.autoExtendSession || options.autoExtendSession

Check failure on line 170 in src/module.ts

View workflow job for this annotation

GitHub Actions / test

Type 'boolean | undefined' is not assignable to type 'boolean'.
logger.withTag('nuxt-auth-utils').info(`Using session storage type: ${runtimeConfig.useSessionStorageType}`)
if (runtimeConfig.useSessionStorageType === 'memory') {
logger.warn('Using in-memory session storage, this is not recommended for production')
if (!nuxt.options.dev) {
logger.error('You are not running in dev mode, please make sure this is intentional')
}
}

// OAuth settings
runtimeConfig.oauth = defu(runtimeConfig.oauth, {})
// GitHub OAuth
Expand Down
Loading
Loading