Release notes v0.0.172
Since Compas v0.0.14 we have been working on a session management experience that works across the board. Rocking quite some features:
- Allows localhost cookies to develop against a remote staging server
- Completely backend managed session lifecycle, with custom rolling session behaviour
- Secure defaults for production environments
- Quick setup, all kinks worked out.
However, there are two major downsides;
- Quite a few workarounds where added, like the
compas proxy
rewriting cookies, and things likekeepPublicCookie
for the frontend to check if a session may exist before attempting any call. - The cookies don't work in all environments. Where things like mobile apps need to open a webview, login and then hope that the cookies that where returned would be added to api calls from the app.
So we created a new session store accessible via 'fancy session tokens' in a JSON Web Token format. Which doesn't do any assumptions about how tokens are transported over the network. Shifting responsibility for secure transport, storage on the client and refreshing the session to the end user of Compas, you 😃
Session store
Migration to the new session store is quite the task. Let's start by removing the deprecated components:
- Remove any reference to
session
imported from@compas/server
. This is the Koa middleware that currently powers the full cookie management. There is no replacement in@compas/server
. - Remove any reference
newSessionStore
imported from@compas/store
. This component was used to persist sessions created by the session middleware in PostgreSQL.
The last session detail related to the old session management is ctx.session
used in your route handlers. It is up to you if you want to replace it completely or if you want to use the new session management functions to be based on ctx.session
. The next steps will describe current behaviours of ctx.session
and which sessionStoreXxx
calls relate to that behaviour. Please refer to the session docs for details on the functions mentioned below.
Creating a session
Previously creating a session could be done by setting ctx.session
to a new object. This would automatically persist the session and set cookies in the response. This is now powered by sessionStoreCreate
and the returned tokens need to manually be transported to the client. It is advised to return the tokens in the response body.
Reading the session
Reading the session was as easy as checking ctx.session
. This is a bit more complex in the new system. The application needs to maintain the session id in the request context, because this is needed to for example update the session. By calling sessionStoreGet
you get an object containing id
, checksum
and data
. You could store this information on ctx._session
for example as id
and checksum
are necessary for sessionStoreUpdate
and sessionStoreInvalidate
. But data
is the one you need to populate ctx.session
. We advise to use an Authorization: Bearer xxx
header format for transporting the access token from the client to the server.
Updating a session
The old session management would automatically check if the ctx.session
object did change and persist the new session data if necessary. The new sessionStoreUpdate
call does exactly that, and thus can be called on every request with minimal performance impact.
Removing a session
Previously destroying a session would be done by setting ctx.session
to null
. This is now handled via sessionStoreInvalidate
which revokes all tokens related to the session.
Custom max age
The previous session management had features to set a custom _maxAge
on ctx.session
. This is not supported anymore.
Sliding session window
The default settings for session management did set up a rolling session system. Meaning that sessions would automatically be renewed on incoming requests if necessary. This is not possible in the new system. Your api need to have some way of refreshing the session via for example a new endpoint, which internally calls sessionStoreRefreshTokens
. And the api clients need to check the exp
field on the access and refresh tokens to decide if they should call the refresh endpoint.
Cleaning up old sessions
Previously we exposed a .clean()
function on the session store returned from newSessionStore
. This would remove all session records PostgreSQL that where expired. The new system has the same feature via sessionStoreCleanupExpiredSessions
.
Migration
The new session store is of course backed by Postgres, and needs the following migration:
CREATE TABLE "sessionStore"
(
"id" uuid PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
"checksum" varchar NOT NULL,
"data" jsonb NOT NULL,
"revokedAt" timestamptz NULL,
"createdAt" timestamptz NOT NULL DEFAULT now(),
"updatedAt" timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE "sessionStoreToken"
(
"id" uuid PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
"session" uuid NOT NULL,
"expiresAt" timestamptz NOT NULL,
"refreshToken" uuid NULL,
"revokedAt" timestamptz NULL,
"createdAt" timestamptz NOT NULL,
CONSTRAINT "sessionStoreTokenSessionFk" FOREIGN KEY ("session") REFERENCES "sessionStore" ("id") ON DELETE CASCADE,
CONSTRAINT "sessionStoreTokenRefreshTokenFk" FOREIGN KEY ("refreshToken") REFERENCES "sessionStoreToken" ("id") ON DELETE CASCADE
);
CREATE INDEX "sessionStoreTokenSessionIdx" ON "sessionStoreToken" ("session");
CREATE INDEX "sessionStoreTokenRefreshTokenIdx" ON "sessionStoreToken" ("refreshToken");
In closing
Since this is a big change, we still support sessionMiddleware
, newSessionStore
, etc in v0.0.172. And most likely in v0.0.173 as well, but it will be removed in a future release.