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 proxyrewriting cookies, and things like
keepPublicCookiefor 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 😃
Migration to the new session store is quite the task. Let's start by removing the deprecated components:
- Remove any reference to
@compas/server. This is the Koa middleware that currently powers the full cookie management. There is no replacement in
- Remove any reference
@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
data. You could store this information on
ctx._session for example as
checksum are necessary for
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
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
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
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");
Since this is a big change, we still support
newSessionStore, etc in v0.0.172. And most likely in v0.0.173 as well, but it will be removed in a future release.