The definition: token(), issue(), open()
What a session definition does besides use(): reading the raw token, minting a token without a request, and opening a token by hand. The same four members on every entry.
import { defineJWSSession, generateJWK } from "unjwt/session";
const key = await generateJWK("HS256");
const useSession = defineJWSSession({ key, maxAge: "15m" });
// in a handler
const session = await useSession.use(event); // the framework's event, see Frameworks
await session.update({ user: "alice" });
// session.id: "a1d9390e-…", session.token: "eyJraWQiOiJ1UklV…"
// in the next request
useSession.token(event); // "eyJraWQiOiJ1UklV…"
// anywhere, no request needed
const token = await useSession.issue({ user: "bob" });
const opened = await useSession.open(token);
// { id: "7246385f-…", createdAt: 1792068521000, expiresAt: 1792069421000, data: { user: "bob" } }unjwt/session and the Elysia entry export defineJWSSession() and defineJWESession(), and each returns the same four members; use() and token() take the framework's event, a context on Elysia. The signatures are on Web standard sessions.
#What each one does
| Member | Reads request | Writes cookie | Fires hooks | Returns |
|---|---|---|---|---|
use(event) | once | on update(), clear(), rolling, or to drop an expired or unreadable token, when the token came from the cookie | onRead, onExpire, onError, then onUpdate and onClear | The session manager. |
token(event) | yes | no | none | The raw token, or undefined. |
issue(data, options?) | no | no | none | A compact token for data. |
open(token) | no | no | key lookup only | { id, createdAt, expiresAt, data }. |
The manager's update() and clear() are the writes. update() fires onUpdate (or onError when issuing fails), and clear() fires onClear.
#token()
Returns the token the session would read: the token of this request's session once use() has read, updated or cleared it, else the session header's when the request carries the header, else a cookie this response already set, then the request cookie (chunks joined). A session header without a token returns undefined, never the cookie. After a clear() in the same request it returns undefined, header included. It checks nothing. Use it to forward the token to another service that verifies it on its own.
#issue()
Mints a compact token for data, with a fresh jti (from generateId), iat now and exp from maxAge. options.maxAge sets the lifetime of this token alone. It reads no request, writes no cookie and fires no hooks, so it runs outside a handler as well.
jti, iat, exp and nbf in data are dropped. A __proto__, constructor or prototype key throws ERR_INVALID_OPTION.
A session with cookie: false and sessionHeader: false has no token source, and issue() is what it is for: a token handed out by some other channel, such as a magic link or an email verification link.
const magicLink = defineJWSSession({
key,
maxAge: "15m",
cookie: false,
sessionHeader: false,
});
const token = await magicLink.issue({ email: "alice@example.com" });
// send `https://app.example/login?token=${token}`
// in the login handler
const { data } = await magicLink.open(getQuery(event).token);
// { email: "alice@example.com" }To hand the client the token of the request's own session, return session.token from the manager after update().
#open()
Opens a token that did not come from this request's cookie or header, such as one posted in a body, passed in a link or received over a WebSocket. The checks are those of a session read: the session typ, the pinned alg (and enc), jti and iat, and, when maxAge is set, exp and iat + maxAge. The request's session is left alone, and only the key lookup hook runs.
const { id, createdAt, expiresAt, data } = await useSession.open(token);
// id: "7246385f-…", createdAt: 1792068521000, expiresAt: 1792069421000, data: { user: "bob" }It resolves to { id, createdAt, expiresAt, data }, typed OpenedSessionState<T, MaxAge>. id and createdAt are always set, data is typed by T, and expiresAt is a number when maxAge is in the config.
onVerifyKeyLookup and onUnsealKeyLookup receive the event only when the token came through use(). open() passes none, so they get the header and the config alone and the event is typed as optional there: open() works anywhere, and a request's own token goes through use().
Failures throw, since there is no onError to receive them:
| Input | Throws |
|---|---|
An expired session token, or one past iat + maxAge | ERR_JWT_EXPIRED |
| A malformed token | ERR_SESSION_TOKEN_INVALID, cause ERR_JWS_INVALID |
| A tampered JWE | ERR_SESSION_TOKEN_INVALID, cause ERR_JWE_DECRYPTION_FAILED |
| A throw from key lookup | ERR_SESSION_TOKEN_INVALID, cause ERR_JWK_KEY_NOT_FOUND |
#update() and clear()
The manager use() returns does the writing. session.update(data?, { maxAge? }) issues a new token and writes the cookie, or, on a session read from the session header, leaves the new token on session.token alone; inside a hook, session.update() on the manager the hook receives does the same thing. A hook that only has the event, such as onExpire, opens the manager with use():
const refreshSession = defineJWESession({ key: refreshKey, name: "refresh_token" });
const accessSession = defineJWSSession({
key,
name: "access_token",
maxAge: "15m",
hooks: {
async onExpire({ event }) {
const refresh = await refreshSession.use(event);
if (refresh.data.sub) {
await (await accessSession.use(event)).update({ sub: refresh.data.sub });
}
},
},
});session.clear() expires the cookie (none on a session read from the header) and leaves an empty session for the rest of the request. onClear receives the session as it stood in this request, so a request that carried no session passes oldSession: undefined.
#Errors
These members throw the same codes as use(). use() and token() also throw ERR_SESSION_CONFIG_INVALID when cookie and sessionHeader are both false; issue() and open() work on such a session. open() throws where use() reports to hooks, as listed above.