No commit activity in last 3 years
No release in over 3 years
Simple token+secret authentication gem for use with has_secure_password for one-way encrypted secrets.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 0.11
~> 10.0
~> 3.0

Runtime

~> 1.0
 Project Readme

TokenSecretAuth

Build Status

TokenSecretAuth aims to be a simple implementation of token+secret authentication.

Clients using this can send a token + secret that looks like: { token: "koV3Zel321fe", secret: "fffixk5ptz2puaf1sk3wo5szpkrpjnhp" }

  • token is a hashed form of the model id

  • secret is randomly assigned and can be encrypted using has_secure_password or a similar implementation.

Why should I use this?

OR Why did you release this?

Many APIs use a token or a token+secret to authenticate API clients. If that token+secret grants similar power as a login+password it should be protected just like a password

However, while looking for a gem to handle mobile API authentication I found that many solutions fit into one of two categories:

  1. They required Devise
  2. They did not encrypt the token (or secret) properly

Other benefits

  • It's plain-old ruby - any model that responds to .find(id) and works with or in a manner similar to has_secure_password will work. (model instance should respond to model_instance#password=)
  • Flexible: you can send token+secret in the URL or in the header or any other way you can interpret in a controller method.

Installation

Add this line to your application's Gemfile:

gem 'token_secret_auth'

And then execute:

$ bundle

Or install it yourself as:

$ gem install token_secret_auth

In your model file add:

include TokenSecretAuth

This grants your model instances the following methods: #token, #decode_token, #generate_secret

Also add to the model:

has_secure_password

Create and run a migration to add the password_digest field to your model.
For example on rails:

$ rails generate migration AddPasswordDigestToApiClients password_digest:string

Note: you do not need a 'token' field on your model. #token is a virtual attribute derived from the model ID. password is your secret.

Usage

Getting the token

Tokens are generated from the model ID.

SomeModel.find(1000).token # => o9WM01OR1jgD

Generating a secret

Secrets are randomly generated by Model.generate_secret or #generate_secret. Store the secret using #password= or similar encrypted functionality.

client = ApiClient.find_by_token('afuoisjdjl') # or  ApiClient.new
client.password = client.generate_secret
client.save # bcrypt/has_secure_password will handle encryption

On Rails you may want to use callbacks to generate the password automatically:

  before_validation :generate_secret, on: [ :new, :create ]

Calling generate_secret on an instance will automatically set password to the new secret.

Passing token+secret to client

If your API allows a client to login and then receive a new API token+secret the responding controller method may look something like this.

 def try_login
    @email = login_params[:email]
    @pass  = login_params[:password]
    @user = User.find_by(email: @email).try(:authenticate, @pass)
    if @user
      api_client = @user.api_clients.create
      # IMPORTANT - this is the only time you can see the secret decrypted
      render json: { token: api_client.token, secret: api_client.secret }
    else
      render json: {password: ["Invalid account or password"]}, status: :unauthorized
    end
  end

To perform authentication with the token + secret, for example as a before_action in an ApplicationController:

def current_user
  if params[:credentials]
    token = credentials_params[:token]
    secret = credentials_params[:secret]
  begin
    api_client = ApiClient.authenticate_by_credentials(token, secret)
  rescue ActiveRecord::RecordNotFound
  # if someone sends a decodable token but the record doesn't exist
  end
end

  if api_client
    @current_user = api_client.user
  else
    render json: {errors: ['Unauthorized token or secret']}, status: :unauthorized }
  end
end

salt

If you'd like to change the salt used for hashing IDs to generate tokens you can add an initializer:

# config/initializers/token_secret_auth.rb

TokenSecretAuth.configure do |config|
    config.id_salt = 'some appropriately salty bytes'
end

Note: the salt used for hashing the secret (password) is controlled by your BCrypt config.

FAQ

Why not devise?

Devise is a great solution and I often recommend it. However, it is sometimes more complexity than needed for something this simple. TokenSecretAuth does not require devise.

Why do I need to encrypt my API token?

If your token or token+secret grants a user similar power as a login+password then it should be encrypted with one-way encryption just like a password.

How do I get the secret?

The secret is the password field on the model. It's only available when the instance is first created after that you can't retrieve it - - that's the whole point of one-way encryption.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/tgaff/token_secret_auth. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the TokenSecretAuth project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.