This post is going to demonstrate how to set up a central tokens table for your Rails application, with the goal to better organize access to resources in your application.
If you did not have a centralized tokens table in your Rails application then each entity that needed different token auth would have to have its own token column on the model and if that entity needed multiple types of tokens, it would have multiple columns on the model. In practice that looks like:
Instead of having these tokens spread across various domains, let’s create a new tokens database table to house all of these different kinds of tokens and associate them with the application entities they belong to.
The migration file:
class CreateTokens < ActiveRecord::Migration[5.4] def change create_table :tokens do |t| t.string :kind t.datetime :expires_at t.string :token t.integer :tokenable_id t.string :tokenable_type t.timestamps null: false t.index :token t.index [:tokenable_id] end end end
The new table columns above briefly defined:
- tokenable_id - id of the user or account that the token is associated with
- tokenable_type - was the token created for a user or an account.
- token - The actual token string
- expires_at - When to revoke the token
- kind - Synonym for token type (eg :ADMIN_AUTH_TOKEN)
Now we need to set up our application to work with this new tokens table. Let’s first define the Token model. The model does two things:
- Defines two callbacks to set the token and expiry.
- Enables the polymorphic relationships using the
class Token < ApplicationRecord belongs_to :tokenable, polymorphic: true before_create :set_token, :set_expires_in private def set_token self.token = SecureRandom.urlsafe_base64 end def set_expires_in expires_in = case kind.to_sym when :INVITE_TOKEN then nil when :AUTH_TOKEN then 30.days when :LOGIN_REDIRECT then 1.day else raise StandardError end self.expires_at ||= DateTime.now + expires_in end end
And for the models that are we are going to be able to create tokens for we will need to define the other side of the relationship. I’ll use Account as an example:
class Account < ApplicationRecord has_many :tokens, as: :tokenable, dependent: :destroy end
Now that we have both sides of the relationship setup to test, load the Rails console and try it out. Let’s create a Token for an Account and then try to look it up.
=> Token.create(tokenable_type: Account, tokenable_id: 1, kind: :LOGIN_REDIRECT) #<Token:0x0018 id: 1 ….>
=> Account.find(1).tokens.find_by(kind: :LOGIN_REDIRECT) #<Token:0x0018 id: 1 ….>
This is a good example of a refactoring opportunity. If your application has different tokens spread across various domains consider consolidating into a central database table and using the power of Rails polymorphism to make your code cleaner.