TokenSecretAuth
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:
- They required Devise
- 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 tohas_secure_password
will work. (model instance should respond tomodel_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 token
s 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.