Hackernoon logoFoal Framework 2.0: What's New? by@loicpoullain

Foal Framework 2.0: What's New?

Loïc Poullain Hacker Noon profile picture

@loicpoullainLoïc Poullain

Fullstack developper. Creator of FoalTS.

This article presents some improvements introduced in version 2 of FoalTS:

  • the JWT utilities to manage secrets and RSA keys,
  • the JWT utilities to manage cookies,
  • and the new stateless CSRF protection.

JWT - Accessing config secrets and public/private keys

Starting from version 2, there is a standardized way to provide and retrieve JWT secrets and RSA public/private keys: the functions

getSecretOrPublicKey
and
getSecretOrPrivateKey
.

Using secrets

In this example, a base64-encoded secret is provided in the configuration.

JWT_SECRET="Ak0WcVcGuOoFuZ4oqF1tgqbW6dIAeSacIN6h7qEyJM8="
settings:
  jwt:
    secret: "env(JWT_SECRET)"
    secretEncoding: base64

Both

getSecretOrPublicKey
and
getSecretOrPrivateKey 
functions will return the secret.

In the case a

secretEncoding
value is provided, the functions return a buffer which is the secret decoded with the provided encoding.

Using public and private keys

const { Env } = require('@foal/core');
const { readFileSync } = require('fs');

module.exports = {
  settings: {
    jwt: {
      privateKey: Env.get('RSA_PRIVATE_KEY') || readFileSync('./id_rsa', 'utf8'),
      publicKey: Env.get('RSA_PUBLIC_KEY') || readFileSync('./id_rsa.pub', 'utf8'),
    }
  }
}

In this case,

getSecretOrPublicKey
and
getSecretOrPrivateKey
return the keys from the environment variables
RSA_PUBLIC_KEY
and
RSA_PRIVATE_KEY
if they are defined or from the files
id_rsa
and
id_rsa.pub
otherwise.

Managing JWT with cookies

In version 2, Foal provides two dedicated functions to manage JWT with cookies. Using these functions instead of manually setting the cookie has three benefits:

  • they include a CSRF protection (see section below),
  • the function
    setAuthCookie
    automatically sets the cookie expiration based on the token expiration,
  • and cookie options can be provided through the configuration.

api.controller.ts

import { JWTRequired } from '@foal/jwt';

@JWTRequired({ cookie: true })
export class ApiController {
  // ...
}

auth.controller.ts

export class AuthController {

  @Post('/login')
  async login(ctx: Context) {
    // ...

    const response = new HttpResponseNoContent();
    // Do not forget the "await" keyword.
    await setAuthCookie(response, token);
    return response;
  }

  @Post('/logout')
  logout(ctx: Context) {
    // ...

    const response = new HttpResponseNoContent();
    removeAuthCookie(response);
    return response;
  }

}

Configuration file

settings:
  jwt:
    cookie:
      name: mycookiename # Default: auth
      domain: example.com
      httpOnly: true # Warning: unlike session tokens, the httpOnly directive has no default value.
      path: /foo # Default: /
      sameSite: strict # Default: lax if settings.jwt.csrf.enabled is true.
      secure: true

Stateless CSRF protection simplified

In version 1, providing a CSRF protection was quite complex. We needed to provide another secret, generate a stateless token, manage the CSRF cookie (expiration, etc), use an additional hook, etc.

Starting from version 2, the CSRF protection is all managed by

@JWTRequired
,
setAuthCookie
and
removeAuthCookie
.

The only thing that you have to do it to enable it through the configuration:

settings:
  jwt:
    csrf:
      enabled: true

When it is enabled, an additional

XSRF-TOKEN
cookie is sent to the client at the same time as the auth cookie (containing your JWT). It contains a stateless CSRF token which is signed and has the same expiration date as your JWT.

When a request is made to the server, the

@JWTRequired
hooks expects you to include its value in the
XSRF-TOKEN
header.

Tags

Join Hacker Noon

Create your free account to unlock your custom reading experience.