DEV Community

loading...
Cover image for Node.JS - Foal framework - What's new in version 2 (3/4)

Node.JS - Foal framework - What's new in version 2 (3/4)

loicpoullain profile image Loïc Poullain ・2 min read

This article was originally published on https://foalts.org/blog/2021/03/02/whats-new-in-version-2-part-2.

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.

New JWT utilities

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.

.env

JWT_SECRET="Ak0WcVcGuOoFuZ4oqF1tgqbW6dIAeSacIN6h7qEyJM8="
Enter fullscreen mode Exit fullscreen mode

Config file

settings:
  jwt:
    secret: "env(JWT_SECRET)"
    secretEncoding: base64
Enter fullscreen mode Exit fullscreen mode

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'),
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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 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.

Example

api.controller.ts

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

@JWTRequired({ cookie: true })
export class ApiController {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

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;
  }

}
Enter fullscreen mode Exit fullscreen mode

Cookie options

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
Enter fullscreen mode Exit fullscreen mode

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:

Config file

settings:
  jwt:
    csrf:
      enabled: true
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (0)

pic
Editor guide