This is a mix of Simple Token Authentication and JWT, based on Devise.
Add the gem to your Gemfile
:
# Gemfile
gem 'jwt_authentication', github: 'Rezonans/jwt_authentication'
Make the models token authenticatable
First define which model or models will be token authenticatable (typ. User
):
# app/models/user.rb
class User < ActiveRecord::Base
acts_as_jwt_authenticatable
# Note: you can include any module you want. If available,
# token authentication will be performed before any other
# Devise authentication method.
#
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :invitable, :database_authenticatable,
:recoverable, :rememberable, :trackable, :validatable,
:lockable
# ...
end
Method acts_as_jwt_authenticatable
extends Model with several methods: :jwt_token
, :generate_authentication_token!
and some others. Obviously, :jwt_token
returns token for current record and :generate_authentication_token!
updates record with new authentication_token.
If the model or models that you have chosen does not contain :authentication_token
column, then add the new column onto it (with index):
rails g jwt_authentication MODEL
This will add 'acts_as_jwt_authenticatable' to specified MODEL. Also, this will generate migration for adding 'authentication_token' to MODEL. To skip generating migration add '-m' parameter: rails g jwt_authentication User -m. Migration looks like:
def change
add_column :users, :authentication_token, :string
add_index :users, :authentication_token
end
Define controllers, which will handle jwt authentication (typ. HomeController
) for which jwt authenticatable models:
# app/controllers/home_controller.rb
class HomeController < ActionController::Base # or ActionController::API
# ...
acts_as_jwt_authentication_handler
# Note: you can specify several parameters for handling authentication for this controller:
# :models (which "acts as jwt authenticatable") for authenticating, hash, that specifies models
# and those authentication parameters :header_name, :param_name, :sign_in
#
# example:
# acts_as_jwt_authentication_handler models: {terminal: {header_name: 'terminal_auth_token',
# param_name: 'X-Auth-Terminal-Token',
# sign_in: :simplified}
#
# ...
end
Method acts_as_jwt_authentication_handler
extends controller with methods: :jwt_authenticate_user
, :jwt_authenticate_user!
and with another ones.
Instead of user there will be specified model names, pair of methods for each model.
See detailed parameters and methods description in Authentication
Atfer controller was extended with jwt_authentication helpers, you may authenticate entity in actions or in before filter:
class TerminalsController < ActionController
acts_as_jwt_authentication_handler models: {terminal: {sign_in: :simlified}}
before_filter :jwt_authenticate_terminal!
def show
@terminal
end
end
Define devise routes for creating devise mapping.
# config/routes.rb
...
devise_for :users, module: :jwt_authentication
...
Devise routing is necessary, because it creates devise mappings.
Some aspects of the behavior of Jwt Authentication can be customized with an initializer. Below is an example with reasonable defaults:
# config/initializers/jwt_authentication.rb
JwtAuthentication.configure do |config|
#
# # Configure models, that will be default for `acts_as_jwt_authentication_handler` calling.
# # Note: specified model should have `authentication_token` attribute (Model should "act as jwt authenticatable")
# # header_name - name of header to search auth_token within request
# # param_name - name of parameters to search auth_token within request
# # sign_in - method to be executed if authentication success, possible values: :devise, :simplified
# # if :devise selected, devises method sign_in() will be called at success authentication,
# # if :simplified selected, instance variable with name of resource will be set (@user or @terminal)
# config.models = {user: {header_name: 'X-User-Token',
# param_name: 'user_token',
# sign_in: :devise}}
#
# # Configure mark of jwt timeout verification
# config.jwt_timeout_verify = true
#
# # Configure jwt timeout leeway (value in seconds)
# config.jwt_timeout_leeway = 60
#
# # Configure jwt timeout for simple login (without "remember me")
# # Devise SessionsController generates jwt according to this parameter
# # * This parameter may be overridden in each model:
# # acts_as_jwt_authenticatable jwt_timeout: 10.minutes
# config.jwt_timeout = 20.minutes
#
# # Configure jwt timeout for session login (with "remember me")
# # Devise SessionsController generates jwt according to this parameter
# # * This parameter may be overridden in each model:
# # acts_as_jwt_authenticatable jwt_timeout_remember_me: 1.week
# config.jwt_timeout_remember_me = 1.month
#
# # Configure list of model keys to be stored in jwt payload.
# # Also, record will be searched by this fields at authentication.
# # * This parameter may be overridden in each model:
# # acts_as_jwt_authenticatable key_fields: [:email, :id]
# config.key_fields = [:email]
#
end
As there was mentioned in Using, method acts_as_jwt_authentication_handler
adds to controller two methods:
:jwt_authenticate_user
and :jwt_authenticate_user!
. Method with bang raises error, if authentication falls,
method without bang do nothing if authentication falls.
Authentication process is pretty simple:
- Analize request - try to find token either in params or header. If token is not found, authentication falls.
- Read payload from jwt
- Search for entity by field, that payload contains. If entity is not found, authentication falls.
- Decode jwt with entities
authentication_token
(private key, that is stored as entities field). Ifjwt_timeout_verify
specified, timeout verification will take place also. - If token successfully verified - sign_in handler will be called, otherwise authentication falls.
sign_in_handler
. You may specify, what to do at success authentication in sign_in
parameter in model:
# config/initializers/jwt_authentication.rb
...
config.models = {user: {sign_in: :devise}}
...
There are 2 variants:
:devise
(default) -:sign_in
(devise controller method) will be called:simplified
- create instance variable with resource name (@user, @terminal, etc).
JwtAuthentication inherits devise controllers: Registrations, Confirmations, Sessions, Passwords. So, you can extend this functionality with inheritance or override of some of them. Note, that you need to specify routes to this inherited controllers, like here:
# config/routes.rb
...
devise_for :users, module: :jwt_authentication
...
Note: request format will be set to :json
by before filter :set_request_format!
, that is plugged to each inherited devise controller.
It is necessary for process action if warder.authenticate!
falls. It will render view for sessions creating by default,
but in our case we need json response :unauthorized