- Notifications
You must be signed in to change notification settings - Fork1
JWT Rails 6+ API authentication gem, re-implementation of Knock.
License
Mayurifag/knock_knock
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
knock_knock
is an authentication solution for Rails API-only application basedon JSON Web Tokens.
This gem is a re-implementation ofKnock
gem to work with Rails 6 (andzeitwerk) with some major and minor changes, i.e. 422 on wrong password insteadof 404.
I'm trying so stay with asame interface on this gem.
- It's lightweight.
- It's tailored for Rails API-only application.
- It'sstateless.
- It works out of the box withAuth0.
Add this line to your application's Gemfile:
gem'knock_knock'
Then execute:
$ bundle install
Finally, run the install generator:
$ rails generate knock_knock:install
It will create the following initializerconfig/initializers/knock_knock.rb
.This file contains all of the existing configuration options.
If you don't use an external authentication solution like Auth0, you also needto provide a way for users to sign in:
$ rails generate knock_knock:token_controller user
This will generate the controlleruser_token_controller.rb
and add therequired route to yourconfig/routes.rb
file.
You can also provide another entity instead ofuser
. E.g.admin
knock_knock
makes one assumption about your user model:
It must have anauthenticate
method, similar to the one added byhas_secure_password.
classUser <ActiveRecord::Basehas_secure_passwordend
Usinghas_secure_password
is recommended, but you don't have to as long asyour user model implements anauthenticate
instance method with the samebehavior.
Include theKnockKnock::Authenticable
module in yourApplicationController
classApplicationController <ActionController::APIincludeKnockKnock::Authenticableend
You can now protect your resources by callingauthenticate_user
as a before_actioninside your controllers:
classSecuredController <ApplicationControllerbefore_action:authenticate_userdefindex# etc...end# etc...end
You can access the current user in your controller withcurrent_user
.
If no valid token is passed with the request, KnockKnock will respond with:
head :unauthorized
You can modify this behaviour by overridingunauthorized_entity
in your controller.
You also have access directly tocurrent_user
which will try to authenticateor returnnil
:
defindexifcurrent_user# do somethingelse# do something elseendend
Note: theauthenticate_user
method uses thecurrent_user
method. Overwritingcurrent_user
may cause unexpected behaviour.
You can do the exact same thing for any entity. E.g. forAdmin
, useauthenticate_admin
andcurrent_admin
instead.
If you're using a namespaced model, KnockKnock won't be able to infer itautomatically from the method name. Instead you can useauthenticate_for
directly like this:
classApplicationController <ActionController::BaseincludeKnockKnock::Authenticableprivatedefauthenticate_v1_userauthenticate_forV1::Userendend
classSecuredController <ApplicationControllerbefore_action:authenticate_v1_userend
Then you get the current user by callingcurrent_v1_user
instead ofcurrent_user
.
The entity model (e.g.User
) can implement specific methods to providecustomization over different parts of the authentication process.
- Find the entity when creating the token (when signing in)
By default, KnockKnock tries to find the entity by email. If you want to modify thisbehaviour, implement within your entity model a class methodfrom_token_request
that takes the request in argument.
E.g.
classUser <ActiveRecord::Basedefself.from_token_requestrequest# Returns a valid user, `nil` or raise `KnockKnock.not_found_exception_class_name`# e.g.# email = request.params["email"]# self.find_by email: emailendend
- Find the authenticated entity from the token payload (when authenticating a request)
By default, KnockKnock assumes the payload as a subject (sub
) claim containing the entity's idand callsfind
on the model. If you want to modify this behaviour, implement withinyour entity model a class methodfrom_token_payload
that takes thepayload in argument.
E.g.
classUser <ActiveRecord::Basedefself.from_token_payloadpayload# Returns a valid user, `nil` or raise# e.g.# self.find payload["sub"]endend
- Modify the token payload
By default the token payload contains the entity's id inside the subject (sub
) claim.If you want to modify this behaviour, implement within your entity model an instance methodto_token_payload
that returns a hash representing the payload.
E.g.
classUser <ActiveRecord::Basedefto_token_payload# Returns the payload as a hashendend
The initializerconfig/initializers/knock_knock.rbis generated whenrails g knock_knock:install
is executed. Each configurationvariable is documented with comments in the initializer itself.
Example request to get a token from your API:
POST /user_token{"auth": {"email": "foo@bar.com", "password": "secret"}}
Example response from the API:
201 Created{"jwt": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"}
To make an authenticated request to your API, you need to pass the token via therequest header:
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9GET /my_resources
KnockKnock responds with a422 Unauthorized
when the user cannot be found orthe password is invalid. This is a security best practice to avoid giving awayinformation about the existence or not of a particular user.
NB: HTTPS should always be enabled when sending a password or token in yourrequest.
To authenticate within your tests:
- Create a valid token
- Pass it in your request
e.g.
classSecuredResourcesControllerTest <ActionDispatch::IntegrationTestdefauthenticated_headertoken=KnockKnock::AuthToken.new(payload:{sub:users(:one).id}).token{'Authorization':"Bearer#{token}"}endit'responds successfully'dogetsecured_resources_url,headers:authenticated_headerassert_response:successendend
If no ActiveRecord is used, then you will need to specify what Exception will beused when the user is not found with the given credentials.
KnockKnock.setupdo |config|# Exception Class# ---------------## Configure the Exception to be used (raised and rescued) for User Not Found.# note: change this if ActiveRecord is not being used.## Default:config.not_found_exception_class_name='MyCustomException'end
The JWT spec supports different kind of cryptographic signing algorithms.You can settoken_signature_algorithm
to use the one you want in theinitializer or do nothing and use the default one (HS256).
You can specify any of the algorithms supported by thejwt gem.
If the algorithm you use requires a public key, you also need to settoken_public_key
in the initializer.
To migrate fromknock
you just need replace all things in your code fromknock
toknock_knock
and fromKnock
toKnockKnock
.
You may look atthis commitfor a reference.
To enable cross-origin resource sharing, check out therack-cors gem.
The gem is available as open source under the terms of theMIT License.
[] Readme and gemspec summaries etc +badges +ci beautify[] changelog[] COC
About
JWT Rails 6+ API authentication gem, re-implementation of Knock.