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: theSHA256digests of the token is stored in the databaseencrypted: the token is stored encrypted in the database using the AES 256 GCM algorithminsecure: 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,
routable_token : { ... }
endStorage strategies
encrypted: :required: Stores the encrypted token in thetoken_field_encryptedcolumn. Thetoken_field_encryptedcolumn needs to exist. We strongly encourage to use this strategy.encrypted: :migrating: Stores the encrypted and plaintext tokens intoken_field_encryptedandtoken_field. Always reads the plaintext token. This should be used while an attribute is transitioning to be encrypted. Bothtoken_fieldandtoken_field_encryptedcolumns need to exist.encrypted: :optional: Stores the encrypted token in thetoken_field_encryptedcolumn. Reads fromtoken_field_encryptedfirst and fallbacks totoken_field. Nullifies the plaintext token in thetoken_fieldcolumn when writing the encrypted token. Bothtoken_fieldandtoken_field_encryptedcolumns need to exist.digest: true: Stores the token’s digest in the database. Thetoken_field_digestcolumn 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.
The token_field_encrypted column should always be indexed, because it is used to perform uniqueness checks and lookups on the token.
Routable token
Use the routable_token option to encode routing information directly into the token.
This allows the system to route requests to the correct cell or shard without additional database lookups.
Routable tokens follow the Routable Tokens design document.
Define the routable_token: option with a hash containing:
if:: A proc that receives the token owner record. The proc typically includes a feature flag check or other conditions. If the proc returnsfalse, a random token is generated usingDevise.friendly_token.payload:: A{ key => proc }hash that defines which routing information to encode in the token. Each proc receives the token owner record and returns the value for that key. At least one of Cell ID (c) or Organization ID (o) must be present in the final payload. Supported keys are:c: Cell ID (included by default ifGitlab.config.cell.idis configured)o: Organization IDg: Group IDp: Project IDu: User IDt: Runner type (for example,t:1for instance type,t:2for group type,t:3for project type)
For an example, see the Routable Tokens design document.
Other options
unique: false: Doesn’t enforce token uniqueness and disables the generation offind_by_token_field(wheretoken_fieldis the attribute name). Default istrue.format_with_prefix: :compute_token_prefix: Allows to define a prefix for the token. The#compute_token_prefixmethod needs to return aString. Default is no prefix. See guidance on token prefixes.expires_at: :compute_token_expiration_time: Allows to define a time when the token should expire. The#compute_token_expiration_timemethod needs to return aTimeobject. Default is no expiration.token_generator:A proc that returns a token. If absent, a random token is generated usingDevise.friendly_token.require_prefix_for_validation:(only for the:encryptedstrategy): Checks that the token prefix matches the expected prefix. If the prefix doesn’t match, it behaves as if the token isn’t set. Defaultfalse.
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