Pretty simple and performance session middleware for koa using immutability.
$ npm install koa-imsession
ctx.session = { id: 1, state: 'pending' }
Session data is immutable. Set the session to a new object.
const oldSession = ctx.session
ctx.session = { ...oldSession, state: 'activated' }
ctx.session = false // the `Set-Cookie` header will be sent to remove the cookie
ctx.session = true // a new session ID is generated and the existing session data is preserved
For session auto-renewal see Redis session store and session auto-renewal.
import { imsession } from 'koa-imsession'
import Koa from 'koa'
const app = new Koa()
/**
* All options are optional, except the `store` MUST be set to a custom store
* (eg. Redis) on production.
*/
const options = {
name: 'connsid', // the name of the session ID cookie, default value is `connsid`
// idResolver, // session ID resolver which gets/sets/generates the session ID
// store, // session store, default value is a `MemoryStore` instance for development
cookie: { // cookie options, see https://github.com/pillarjs/cookies
maxAge: 86400_000, // default value is 1 day
},
}
app.use(imsession(options))
app.use(ctx => {
const views = ctx.session?.views ?? 0
ctx.session = { views: views + 1 } // immutable object
ctx.body = 'views: ' + ctx.session.views
})
app.listen(3000)
The builtin MemoryStore
is used by default for development and testing purpose only. A custom session store must be set on production environment.
import type { SessionStore, SessionData } from 'koa-imsession'
import { TTL_MS } from 'koa-imsession'
import redis from './redis.js' // github.com/redis/ioredis
/**
* A Redis session store.
*/
export class RedisSessionStore<T extends SessionData> implements SessionStore<T> {
/**
* Returns session data and TTL_MS.
*/
async get(sessionId: string): Promise<T | undefined> {
const tx = redis.multi()
tx.get(sessionId) // value
tx.ttl(sessionId) // ttl in seconds
const results = await tx.exec()
if (!results) return
const [[, value], [, ttl]] = results
if (!value) return
const sessionData = JSON.parse(value as string)
if (ttl as number > 0) {
// AUTO RENEWAL happens here!!!
// Set the `TTL_MS` symbol property, koa-imsession will check whether it
// is less than cookie's `maxAge/3`, if true the session will be renewed
// automatically.
sessionData[TTL_MS] = (ttl as number) * 1000
}
return sessionData
}
async set(sessionId: string, sessionData: T, ttlMs: number): Promise<void> {
const data = JSON.stringify(sessionData)
await redis.set(sessionId, data, 'PX', ttlMs)
}
async destroy(sessionId: string): Promise<void> {
await redis.del(sessionId)
}
}
You may want to get the Bearer
access token from the Authorization
header.
import type Cookies from 'cookies'
import type Koa from 'koa'
import { SessionIdResolver as CookieSessionIdResolver } from 'koa-imsession'
export class SessionIdResolver extends CookieSessionIdResolver {
constructor({ name, cookie }: { name: string; cookie?: Cookies.SetOption }) {
super({ name, cookie })
}
// Overrides `get` method to get the access token from header first.
get(ctx: Koa.Context): string | undefined {
return getAccessTokenFromHeader(ctx) ?? super.get(ctx)
}
}
/**
* Gets access token from header.
*/
function getAccessTokenFromHeader(ctx: Koa.Context): string | undefined {
const authorization = ctx.get('Authorization')
if (!authorization) return
// Syntax: Bearer 1*SP b64token
const [scheme, accessToken] = authorization.split(' ', 2)
if (scheme.toLowerCase() === 'bearer')
return accessToken
}