Skip to main content
Core Concept

One session API.
Every transport.

Sessions, permissions, and auth middleware work the same across HTTP, WebSocket, CLI, MCP, and every other wire. Write your security logic once.

Sessions

Read, write, clear. That's the API.

Whether the request arrives over HTTP, WebSocket, or CLI — your function reads and writes the session the same way.

1

Middleware loads

Session populated from cookie, bearer token, or connection state

2

Function reads

Access session via the wire parameter — userId, role, etc.

3

Function modifies

Call setSession() or clearSession() to update

4

Middleware persists

Changes saved back to the transport — cookie, store, etc.

login.func.tssetSession
export const login = pikkuFunc({
auth: false,
func: async (
{ jwt, db },
{ email, password },
{ setSession }
) => {
const user = await db.verifyCredentials(email, password)

// Works the same whether it's an HTTP cookie,
// WebSocket connection, or CLI token
setSession({ userId: user.id, role: user.role })

return { token: jwt.sign({ userId: user.id }) }
}
})
getProfile.func.tssession
export const getProfile = pikkuFunc({
func: async ({ db }, _data, { session }) => {
// session is loaded by middleware before
// your function runs — same API everywhere
return await db.getUser(session.userId)
}
})
logout.func.tsclearSession
export const logout = pikkuFunc({
func: async ({}, _data, { clearSession }) => {
clearSession()
}
})

Permissions

Boolean checks. Composable logic.

Permissions run before your function. Return true to allow, false to reject. Group them with OR/AND logic.

auth.tspikkuAuth
import { pikkuAuth } from '#pikku'

// Session-only checks — receives (services, session)
// Use for authentication gates, role checks, MCP tools, AI agents
export const isAuthenticated = pikkuAuth(
async (_services, session) => !!session
)

export const isAdmin = pikkuAuth(
async (_services, session) => session?.role === 'admin'
)
permissions.tspikkuPermission
import { pikkuPermission } from '#pikku'

// Data-aware checks — receives (services, data, wire)
// Use when authorization depends on the actual request data
export const isOwner = pikkuPermission(
async ({ db }, { bookId }, { session }) => {
const book = await db.getBook(bookId)
return book?.authorId === session?.userId
}
)

export const hasBookAccess = pikkuPermission(
async ({ db }, { bookId }, { session }) => {
return await db.hasAccess(session?.userId, bookId)
}
)
deleteBook.func.tsfunc.ts
export const deleteBook = pikkuFunc({
func: async ({ db }, { bookId }) => {
await db.deleteBook(bookId)
},
// OR logic across keys, AND within arrays
permissions: {
admin: isAdmin, // OR: admins can delete
owner: isOwner, // OR: book author can delete
reviewer: [isAuthenticated, hasBookAccess] // AND: both must pass
}
})

pikkuAuth

Session-only — receives (services, session). Use for authentication gates, role checks, MCP tools, and AI agents.

pikkuPermission

Data-aware — receives (services, data, wire). Use when authorization depends on request data, e.g. ownership or access checks.

OR / AND composition

Each key in the permissions object is an OR group. Wrap in an array for AND logic. If any group passes, the request proceeds.

Auth Middleware

Built-in strategies. Four scopes.

Bearer tokens, cookies, and API keys ship out of the box. Apply middleware globally, by route prefix, by tag, or per-wiring.

authBearer

JWT from the Authorization header. Decodes and sets session automatically.

authCookie

JWT-encoded cookie. Auto-refreshes on session change. Configurable expiry and options.

authAPIKey

Reads x-api-key header or apiKey query param. Decodes as JWT to set session.

middleware.tsbuilt-in
import { authBearer, authCookie, authAPIKey } from '@pikku/core/middleware'
import { addHTTPMiddleware, addHTTPPermission } from '#pikku'

// JWT bearer token — reads Authorization header
addHTTPMiddleware([authBearer()])

// Cookie-based sessions — auto-refreshes JWT
addHTTPMiddleware([
authCookie({
name: 'session',
expiresIn: { value: 30, unit: 'day' },
options: { httpOnly: true, secure: true },
})
])

// API key — from x-api-key header or ?apiKey= query
addHTTPMiddleware([authAPIKey({ source: 'all' })])
scopes.ts4 levels
// Global: all HTTP routes
addHTTPMiddleware('*', [authBearer()])

// Prefix-based: only /admin/* routes
addHTTPMiddleware('/admin/*', [auditLog])
addHTTPPermission('/admin/*', { admin: requireAdmin })

// Tag-based: applies to any wiring with 'api' tag
addMiddleware('api', [rateLimiter])
addPermission('api', { auth: requireAuth })

// Inline: per-wiring
wireHTTP({
route: '/books/:id',
func: getBook,
middleware: [cacheControl],
permissions: { owner: requireOwnership },
})
1

Global

Applies to all wirings of a type

2

Prefix

Route-pattern matching like /admin/*

3

Tag

Any wiring tagged with a keyword

4

Inline

Directly on a single wiring

Secure by default

Scaffold a project with auth middleware pre-configured. Sessions work across every protocol from day one.

$ npm create pikku@latest

MIT Licensed  ·  Works with Express, Fastify, Lambda & Cloudflare