Need help with api_guard?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

Gokul595
199 Stars 16 Forks MIT License 186 Commits 7 Opened issues

Description

JWT authentication solution for Rails APIs

Services available

!
?

Need anything else?

Contributors list

# 188,624
swagger
HTML
CSS
rails-a...
166 commits
# 16,260
Ruby
Rails
skype
Go
1 commit

API Guard

Version Build Status Maintainability

JSON Web Token (JWT) based authentication solution with token refreshing & blacklisting for APIs built on Rails.

This is built using Ruby JWT gem. Currently API Guard supports only HS256 algorithm for cryptographic signing.

Table of Contents

Installation

Add this line to your application's Gemfile:

gem 'api_guard'

And then execute in your terminal:

bash
$ bundle install

Or install it yourself as:

bash
$ gem install api_guard

Getting Started

Below steps are provided assuming the model in

User
.

Creating User model

Create a model for User with below command.

$ rails generate model user name:string email:string:uniq password_digest:string

Then, run migration to create the

users
table.
$ rails db:migrate

Add hassecurepassword in

User
model for password authentication.

Refer this Wiki for configuring API Guard authentication to work with Devise instead of using

has_secure_password
.
class User < ApplicationRecord
  has_secure_password
end

Then, add

bcrypt
gem in your Gemfile which is used by hassecurepassword for encrypting password and authentication.
gem 'bcrypt', '~> 3.1.7'

And then execute in your terminal:

$ bundle install

Configuring Routes

Add this line to the application routes (

config/routes.rb
) file:
api_guard_routes for: 'users'

This will generate default routes such as sign up, sign in, sign out, token refresh, password change for User.

Refer this Wiki for configuring API Guard routes to work with Devise.

Registration

This will create an user and responds with access token, refresh token and access token expiry in the response header.

Example request:

# URL
POST "/users/sign_up"

Request body

{ "email": "[email protected]", "password": "api_password", "password_confirmation": "api_password" }

Example response body:

{
    "status": "success",
    "message": "Signed up successfully"
}

Example response headers:

Access-Token: eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE1NDY3MDgwMjAsImlhdCI6MTU0NjcwNjIyMH0.F_JM7fUcKEAq9ZxXMxNb3Os-WeY-tuRYQnKXr_bWo5E
Refresh-Token: Iy9s0S4Lf7Xh9MbFFBdxkw
Expire-At: 1546708020

The access token will only be valid till the expiry time. After the expiry you need to refresh the token and get new access token and refresh token.

You can customize the parameters of this API by overriding the controller code if needed.

Sign In (Getting JWT access token)

This will authenticate the user with email and password and respond with access token, refresh token and access token expiry in the response header.

To make this work, the resource model (User) should have an

authenticate
method as available in hassecurepassword. You can use hassecurepassword or your own logic to authenticate the user in
authenticate
method.

Example request:

# URL
POST "/users/sign_in"

Request body

{ "email": "[email protected]", "password": "api_password" }

Example response body:

{
    "status": "success",
    "message": "Signed in successfully"
}

Example response headers:

The response headers for this request will be same as registration API.

You can customize the parameters of this API by overriding the controller code if needed.

Authenticate API Request

To authenticate the API request just add this before_action in the controller:

before_action :authenticate_and_set_user

Note: It is possible to authenticate with more than one resource, e.g.

authenticate_and_set_user_or_admin
will permit tokens issued for users or admins.

Send the access token got in sign in API in the Authorization header in the API request as below. Also, make sure you add "Bearer" before the access token in the header value.

Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE1NDY3MDgwMjAsImlhdCI6MTU0NjcwNjIyMH0.F_JM7fUcKEAq9ZxXMxNb3Os-WeY-tuRYQnKXr_bWo5E

Then, you can get the current authenticated user using below method:

current_user

and also, using below instance variable:

@current_user

Note: Replace

_user
with your model name if your model is not User.

Refresh access token

This will work only if token refreshing configured for the resource. Please see token refreshing for details about configuring token refreshing.

Once the access token expires it won't work and the

authenticate_and_set_user
method used in before_action in controller will respond with 401 (Unauthenticated).

To refresh the expired access token and get new access and refresh token you can use this request with both access token and request token (which you got in sign in API) in the request header.

Example request:

# URL
POST "/users/tokens"

Request header

Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE1NDY3MDgwMjAsImlhdCI6MTU0NjcwNjIyMH0.F_JM7fUcKEAq9ZxXMxNb3Os-WeY-tuRYQnKXr_bWo5E Refresh-Token: Iy9s0S4Lf7Xh9MbFFBdxkw

Example response body:

{
    "status": "success",
    "message": "Token refreshed successfully"
}

Example response headers:

The response headers for this request will be same as registration API.

Change password

To change password of an user you can use this request with the access token in the header and new password in the body.

By default, changing password will invalidate all old access tokens and refresh tokens generated for this user and responds with new access token and refresh token.

Example request:

# URL
PATCH "/users/passwords"

Request header

Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE1NDY3MDgwMjAsImlhdCI6MTU0NjcwNjIyMH0.F_JM7fUcKEAq9ZxXMxNb3Os-WeY-tuRYQnKXr_bWo5E

Request body

{ "password": "api_password_new", "password_confirmation": "api_password_new" }

Example response body:

{
    "status": "success",
    "message": "Password changed successfully"
}

Example response headers:

The response headers for this request will be same as registration API.

Sign out

You can use this request to sign out an user. This will blacklist the current access token from future use if token blacklisting configured.

Example request:

# URL
DELETE "/users/sign_out"

Request header

Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE1NDY3MDgwMjAsImlhdCI6MTU0NjcwNjIyMH0.F_JM7fUcKEAq9ZxXMxNb3Os-WeY-tuRYQnKXr_bWo5E

Example response:

{
    "status": "success",
    "message": "Signed out successfully"
}

Delete account

You can use this request to delete an user. This will delete the user and its associated refresh tokens.

Example request:

# URL
DELETE "/users/delete"

Request header

Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE1NDY3MDgwMjAsImlhdCI6MTU0NjcwNjIyMH0.F_JM7fUcKEAq9ZxXMxNb3Os-WeY-tuRYQnKXr_bWo5E

Example response:

{
    "status": "success",
    "message": "Account deleted successfully"
}

Configuration

To configure the API Guard you need to first create an initializer using

$ rails generate api_guard:initializer

This will generate an initializer named api_guard.rb in your app config/initializers directory with default configurations.

Default configuration

config/initializers/api_guard.rb

ApiGuard.setup do |config|
  # Validity of the JWT access token
  # Default: 1 day
  # config.token_validity = 1.day

Secret key for signing (encoding & decoding) the JWT access token

Default: 'secret_key_base' from Rails secrets

config.token_signing_secret = 'my_signing_secret'

Invalidate old tokens on changing the password

Default: false

config.invalidate_old_tokens_on_password_change = false

Blacklist JWT access token after refreshing

Default: false

config.blacklist_token_after_refreshing = false

end

Access token validity

By default, the validity of the JWT access token is 1 day from the creation. Override this by configuring

token_validity
config.token_validity = 1.hour # Set one hour validity for access tokens

On accessing the authenticated API with expired access token, API Guard will respond 401 (Unauthenticated) with message "Access token expired".

Access token signing secret

By default, the

secret_key_base
from the Rails secrets will be used for signing (encoding & decoding) the JWT access token. Override this by configuring
token_signing_secret
config.token_signing_secret = 'my_signing_secret'

Note: Avoid committing this token signing secret in your version control (GIT) and always keep this secure. As, exposing this allow anyone to generate JWT access token and give full access to APIs. Better way is storing this value in environment variable or in encrypted secrets (Rails 5.2+)

Invalidate tokens on password change

By default, API Guard will not invalidate old JWT access tokens on changing password. If you need, you can enable it by configuring

invalidate_old_tokens_on_password_change
to
true
.

Note: To make this work, a column named

token_issued_at
with datatype
datetime
is needed in the resource table.
config.invalidate_old_tokens_on_password_change = true

If your app allows multiple logins then, you must set this value to

true
so that, this prevent access for all logins (access tokens) on changing the password.

Token refreshing

To include token refreshing in your application you need to create a table to store the refresh tokens.

Use below command to create a model

RefeshToken
with columns to store the token and the user reference
$ rails generate model refresh_token token:string:uniq user:references

Then, run migration to create the

refresh_tokens
table
$ rails db:migrate

Note: Replace

user
in the above command with your model name if your model is not User.

After creating model and table for refresh token configure the association in the resource model using

api_guard_associations
method
class User < ApplicationRecord
  api_guard_associations refresh_token: 'refresh_tokens'
  has_many :refresh_tokens, dependent: :delete_all
end

If you also have token blacklisting enabled you need to specify both associations as below

api_guard_associations refresh_token: 'refresh_tokens', blacklisted_token: 'blacklisted_tokens'

Token blacklisting

To include token blacklisting in your application you need to create a table to store the blacklisted tokens. This will be used to blacklist a JWT access token from future use. The access token will be blacklisted on successful sign out of the resource.

Use below command to create a model

BlacklistedToken
with columns to store the token and the user reference
$ rails generate model blacklisted_token token:string user:references expire_at:datetime

Then, run migration to create the

blacklisted_tokens
table
$ rails db:migrate

Note: Replace

user
in the above command with your model name if your model is not User.

After creating model and table for blacklisted token configure the association in the resource model using

api_guard_associations
method
class User < ApplicationRecord
  api_guard_associations blacklisted_token: 'blacklisted_tokens'
  has_many :blacklisted_tokens, dependent: :delete_all
end

If you also have token refreshing enabled you need to specify both associations as below

api_guard_associations refresh_token: 'refresh_tokens', blacklisted_token: 'blacklisted_tokens'

And, as this creates rows in

blacklisted_tokens
table you need to have a mechanism to delete the expired blacklisted tokens to prevent this table from growing. One option is to have a CRON job to run a task daily that deletes the blacklisted tokens that are expired i.e.
expire_at < DateTime.now
.

Blacklisting after refreshing token

By default, the JWT access token will not be blacklisted on refreshing the JWT access token. To enable this, you can configure it in API Guard initializer as below,

config.blacklist_token_after_refreshing = true

Overriding defaults

Controllers

You can override the default API Guard controllers and customize the code as your need by generating the controllers in your app

$ rails generate api_guard:controllers users

In above command

users
is the scope of the controllers. If needed, you can replace
users
with your own scope.

This will generate all default controllers for

users
in the directory app/controllers/users.

Then, configure this controller in the routes

api_guard_routes for: 'users', controller: {
  registration: 'users/registration',
  authentication: 'users/authentication',
  passwords: 'users/passwords',
  tokens: 'users/tokens'
}

You can also specify the controllers that you need to generate using

-c
or
--controllers
option.
$ rails generate api_guard:controllers users -c registration authentication

Available controllers: registration, authentication, tokens, passwords

Routes

You can skip specific controller routes generated by API Guard

api_guard_routes for: 'users', except: [:registration]

Above config will skip registration related API Guard controller routes for the resource user.

You can also specify only the controller routes you need,

api_guard_routes for: 'users', only: [:authentication]

Available controllers: registration, authentication, tokens, passwords

Customizing the route path:

You can customize the path of the default routes of the API Guard using the

api_guard_scope
as below,
api_guard_routes for: 'users', except: [:registration]

api_guard_scope 'users' do post 'account/create' => 'api_guard/registration#create' delete 'account/delete' => 'api_guard/registration#destroy' end

Above configuration will replace default registration routes

users/sign_up
&
users/delete
with
account/create
&
account/delete

Adding custom data in JWT token payload

You can add custom data in the JWT token payload in the format of Hash and use the data after decoding the token on every request.

To add custom data, you need to create an instance method

jwt_token_payload
in the resource model as below which should return a Hash,
class User < ApplicationRecord
  def jwt_token_payload
    { custom_key: 'value' }
  end
end

API Guard will add the hash returned by this method to the JWT token payload in addition to the default payload values. This data (including default payload values) will be available in the instance variable

@decoded_token
on each request if the token has been successfully decoded. You can access the values as below,
@decoded_token[:custom_key]

Override finding resource

By default, API Guard will try to find the resource by it's

id
. If you wish to override this default behavior, you can do it by creating a method
find_resource_from_token
in the specific controller or in
ApplicationController
as you need.

Adding custom logic in addition to the default logic:

ruby
def find_resource_from_token(resource_class)
  user = super # This will call the actual method defined in API Guard
  user if user&.active?
end

Using custom query to find the user from the token:

ruby
def find_resource_from_token(resource_class)
  resource_id = @decoded_token[:"#{@resource_name}_id"]
  resource_class.find_by(id: resource_id, status: 'active') if resource_id
end

This method has an argument

resource_class
which is the class (model) of the current resource (
User
). This method should return a resource object to successfully authenticate the request or
nil
to respond with 401.

You can also use the custom data added in the JWT token payload using

@decoded_token
instance variable and customize the logic as you need.

Customizing / translating response messages using I18n

API Guard uses I18n for success and error messages. You can create your own locale file and customize the messages for any language.

en:
  api_guard:
    authentication:
      signed_in: 'Signed in successfully'
      signed_out: 'Signed out successfully'

You can find the complete list of available keys in this file: https://github.com/Gokul595/api_guard/blob/master/config/locales/en.yml

Testing

API Guard comes with helper for creating JWT access token and refresh token for the resource which you can use it for testing the controllers of your application.

For using it, just include the helper in your test framework.

RSpec

If you're using RSpec as your test framework then include the helper in spec/rails_helper.rb file

RSpec.configure do |config|
  config.include ApiGuard::Test::ControllerHelper
end

Minitest

If you're using Minitest as your test framework then include the helper in your test file

include ApiGuard::Test::ControllerHelper

After including the helper, you can use this method to create the JWT access token and refresh token for the resource

jwt_and_refresh_token(user, 'user')

Where the first argument is the resource(User) object and the second argument is the resource name which is

user
.

This method will return two values which is access token and refresh token.

If you need expired JWT access token for testing you can pass the third optional argument value as

true
jwt_and_refresh_token(user, 'user', true)

Then, you can set the access token and refresh token in appropriate request header on each test request.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/Gokul595/api_guard. 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.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.