Hackernoon logoFoalTS v2 Features: An Updated Session System by@loicpoullain

FoalTS v2 Features: An Updated Session System

This article presents the improvements to the session system in FoalTS version 2. It is probably the main new feature of this version. The old session components have been redesigned so as to serve three purposes: be easy to use with very little code, support a large variety of applications and architectures (SPA, Mobile, SSR, API, API) and add missing features impossible to implement in version 1. The new syntax can be used either with cookies or with the new syntax. It adds the following new features: query all sessions of a given user; force logout of a specific user; flash sessions; anonymous and authenticated sessions; auto-create sessions.
image
Loïc Poullain Hacker Noon profile picture

Loïc Poullain

Fullstack developer JS/TS. Creator of FoalTS.

This article presents the improvements to the session system in FoalTS version 2. It is probably the main new feature of this version.

The old session components have been redesigned so as to serve three purposes:

  • be easy to use with very little code,
  • support a large variety of applications and architectures (SPA, Mobile, SSR, API,
    Authorization
    header, cookies, serverless environment, social auth, etc),
  • and add missing features impossible to implement in version 1.

The new syntax can be used either with cookies or with the

Authorization
header. It adds the following new features:

  • query all sessions of a given user
  • query all connected users
  • force logout of a specific user
  • flash sessions
  • session ID regeneration
  • anonymous and authenticated sessions

Here is the way to use it:

  • First specify in the configuration where your sessions should be stored (SQL database, redis, Mongo, etc).
  • Then decorate any route or controller that need authentication with
    @UseSessions
    .

Example with the
Authorization
header

In this first example, we'd like to use the

Authorization
header to handle authentication.

We want to send an email address and password to

/login
and retrieve a token in return to authenticate further requests.

import { dependency, Context, Get, HttpResponseOK, UserRequired, UseSessions, ValidateBody, HttpResponseUnauthorized, Post } from '@foal/core';
import { fetchUser } from '@foal/typeorm';

import { User, Product } from '../entities';

@UseSessions({
  user: fetchUser(User)
})
export class ApiController {
  @dependency
  store: Store;

  @Get('/products')
  @UserRequired()
  async readProducts(ctx: Context<User>) {
    return new HttpResponseOK(Product.find({ user: ctx.user }));
  }

  @Post('/login')
  @ValidateBody({
    additionalProperties: false,
    properties: {
      email: { type: 'string', format: 'email' },
      password: { type: 'string' }
    },
    required: [ 'email', 'password' ],
    type: 'object',
  })
  async login(ctx: Context) {
    const user = await User.findOne({ email: ctx.request.body.email });

    if (!user) {
      return new HttpResponseUnauthorized();
    }

    if (!await verifyPassword(ctx.request.body.password, user.password)) {
      return new HttpResponseUnauthorized();
    }

    ctx.session = await createSession(this.store);
    ctx.session.setUser(user);

    return new HttpResponseOK({
      token: ctx.session.getToken()
    });
  }

  @Post('/logout')
  async logout(ctx: Context) {
    if (ctx.session) {
      await ctx.session.destroy();
    }

    return new HttpResponseOK();
  }
}

Example with cookies

In this second example, we will use cookies to manage authentication. Foal will auto-creates a session when none exists.

import { dependency, Context, Get, HttpResponseOK, UserRequired, UseSessions, ValidateBody, HttpResponseUnauthorized, Post } from '@foal/core';
import { fetchUser } from '@foal/typeorm';

import { User, Product } from '../entities';

@UseSessions({
  cookie: true,
  user: fetchUser(User)
})
export class ApiController {
  @dependency
  store: Store;

  @Get('/products')
  @UserRequired()
  async readProducts(ctx: Context<User>) {
    return new HttpResponseOK(Product.find({ user: ctx.user }));
  }

  @Post('/login')
  @ValidateBody({
    additionalProperties: false,
    properties: {
      email: { type: 'string', format: 'email' },
      password: { type: 'string' }
    },
    required: [ 'email', 'password' ],
    type: 'object',
  })
  async login(ctx: Context) {
    const user = await User.findOne({ email: ctx.request.body.email });

    if (!user) {
      return new HttpResponseUnauthorized();
    }

    if (!await verifyPassword(ctx.request.body.password, user.password)) {
      return new HttpResponseUnauthorized();
    }

    ctx.session.setUser(user);

    return new HttpResponseOK();
  }

  @Post('/logout')
  async logout(ctx: Context) {
    if (ctx.session) {
      await ctx.session.destroy();
    }

    return new HttpResponseOK();
  }
}

Previously published at https://foalts.org/blog/2021/04/08/whats-new-in-version-2-part-4

Tags

Join Hacker Noon

Create your free account to unlock your custom reading experience.