Using the TokenAuthenticatable concern

The TokenAuthenticatable module is a concern that provides token-based authentication functionality for ActiveRecord models. It allows you to define authentication tokens for your models.

Overview

This module provides a flexible way to add token-based authentication to your models.

It supports three storage strategies:

  • digest: the SHA256 digests of the token is stored in the database
  • encrypted: the token is stored encrypted in the database using the AES 256 GCM algorithm
  • insecure: the token is stored as-is (not encrypted nor digested) in the database. We strongly discourage the usage of this strategy.

It also supports several options for each storage strategies.

Usage

To define a token_field attribute in your model, include the module and call add_authentication_token_field:

class User < ApplicationRecord
  include TokenAuthenticatable

  add_authentication_token_field :token_field, encrypted: :required
end

Storage strategies

  • encrypted: :required: Stores the encrypted token in the token_field_encrypted column. The token_field_encrypted column needs to exist. We strongly encourage to use this strategy.
  • encrypted: :migrating: Stores the encrypted and plaintext tokens respectively in token_field_encrypted and token_field. Always reads the plaintext token. This should be used while an attribute is transitioning to be encrypted. Both token_field and token_field_encrypted columns need to exist.
  • encrypted: :optional: Stores the encrypted token in the token_field_encrypted column. Reads from token_field_encrypted first and fallbacks to token_field. Nullifies the plaintext token in the token_field column when writing the encrypted token. Both token_field and token_field_encrypted columns need to exist.
  • digest: true: Stores the token’s digest in the database. The token_field_digest column needs to exist.
  • insecure: true: Stores the token as-is (not encrypted nor digested) in the database. We strongly discourage the usage of this strategy.

By default, the SHA256 digest of the tokens are stored in the database, if no storage strategy is chosen.

Other options

  • unique: false: Doesn’t enforce token uniqueness and disables the generation of find_by_token_field (where token_field is the attribute name). Default is true.
  • format_with_prefix: :compute_token_prefix: Allows to define a prefix for the token. The #compute_token_prefix method needs to return a String. Default is no prefix.
  • expires_at: :compute_token_expiration_time: Allows to define a time when the token should expire. The #compute_token_expiration_time method needs to return a Time object. Default is no expiration.
  • token_generator: A proc that returns a token. If absent, a random token is generated using Devise.friendly_token.
  • routable_token:: A hash allowing to define “routable” parts that should be encoded in the token. This follows the Routable Tokens design document. Supported keys are:
    • if:: a proc receiving the token owner record. The proc usually has a feature flag check, and/or other checks. If the proc returns false, a random token is generated using Devise.friendly_token.
    • payload:: A { key => proc } hash with allowed keys c, o, g, p,u which complies with the specification. See an example in the Routable Tokens design document.
  • require_prefix_for_validation: (only for the :encrypted strategy): Checks that the token prefix matches the expected prefix. If the prefix doesn’t match, it behaves as if the token isn’t set. Default false.

Accessing and manipulating tokens

user = User.new
user.token_field # Retrieves the token
user.set_token_field('new_token') # Sets a new token
user.ensure_token_field # Generates a token if not present
user.ensure_token_field! # Generates a token if not present
user.reset_token_field! # Resets the token and saves the model with #save!
user.token_field_matches?(other_token) # Securely compares the token with another
user.token_field_expires_at # Returns the expiration time
user.token_field_expired? # Checks if the token has expired
user.token_field_with_expiration # Returns a API::Support::TokenWithExpiration object, useful for API response