Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename blacklist #74

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
### 0.2.0 - 2019-04-27

* Features
* Added configurable option to blacklist JWT access token on refreshing as requested in this
* Added configurable option to revoke JWT access token on refreshing as requested in this
[issue comment](https://github.com/Gokul595/api_guard/issues/8#issuecomment-477436164).

### 0.1.3 - 2019-03-26
Expand Down
46 changes: 23 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![Maintainability](https://api.codeclimate.com/v1/badges/ced3e74a26a66ed915cb/maintainability)](https://codeclimate.com/github/Gokul595/api_guard/maintainability)


[JSON Web Token (JWT)](https://jwt.io/) based authentication solution with token refreshing & blacklisting for APIs
[JSON Web Token (JWT)](https://jwt.io/) based authentication solution with token refreshing & revocation for APIs
built on Rails.

This is built using [Ruby JWT](https://github.com/jwt/ruby-jwt) gem. Currently API Guard supports only HS256 algorithm
Expand All @@ -30,7 +30,7 @@ for cryptographic signing.
* [Access token signing secret](#access-token-signing-secret)
* [Invalidate tokens on password change](#invalidate-tokens-on-password-change)
* [Token refreshing](#token-refreshing)
* [Token blacklisting](#token-blacklisting)
* [Token revocation](#token-revocation)
* [Overriding defaults](#overriding-defaults)
* [Controllers](#controllers)
* [Routes](#routes)
Expand Down Expand Up @@ -299,8 +299,8 @@ The response headers for this request will be same as [registration API](#regist

### 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](#token-blacklisting) configured.
You can use this request to sign out an user. This will revoke the current access token from future use if
[token revocation](#token-revocation) configured.

Example request:

Expand Down Expand Up @@ -377,9 +377,9 @@ ApiGuard.setup do |config|
# Default: false
# config.invalidate_old_tokens_on_password_change = false

# Blacklist JWT access token after refreshing
# Revoke JWT access token after refreshing
# Default: false
# config.blacklist_token_after_refreshing = false
# config.revoke_token_after_refreshing = false
end
```

Expand Down Expand Up @@ -461,59 +461,59 @@ class User < ApplicationRecord
end
```

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

```ruby
api_guard_associations refresh_token: 'refresh_tokens', blacklisted_token: 'blacklisted_tokens'
api_guard_associations refresh_token: 'refresh_tokens', revoked_token: 'revoked_tokens'
```

### Token blacklisting
### Token revocation

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
To include token revocation in your application you need to create a table to store the revoked tokens. This will be
used to revoke a JWT access token from future use. The access token will be revoked 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
Use below command to create a model `RevokedToken` with columns to store the token and the user reference

```bash
$ rails generate model blacklisted_token token:string user:references expire_at:datetime
$ rails generate model revoked_token token:string user:references expire_at:datetime
```

Then, run migration to create the `blacklisted_tokens` table
Then, run migration to create the `revoked_tokens` table

```bash
$ 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
After creating model and table for revoked token configure the association in the resource model using
`api_guard_associations` method

```ruby
class User < ApplicationRecord
api_guard_associations blacklisted_token: 'blacklisted_tokens'
has_many :blacklisted_tokens, dependent: :delete_all
api_guard_associations revoked_token: 'revoked_tokens'
has_many :revoked_tokens, dependent: :delete_all
end
```

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

```ruby
api_guard_associations refresh_token: 'refresh_tokens', blacklisted_token: 'blacklisted_tokens'
api_guard_associations refresh_token: 'refresh_tokens', revoked_token: 'revoked_tokens'
```

And, as this creates rows in `blacklisted_tokens` table you need to have a mechanism to delete the expired blacklisted
And, as this creates rows in `revoked_tokens` table you need to have a mechanism to delete the expired revoked
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`.
revoked tokens that are expired i.e. `expire_at < DateTime.now`.

**Blacklisting after refreshing token**
**Revocation after refreshing token**

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

```ruby
config.blacklist_token_after_refreshing = true
config.revoke_token_after_refreshing = true
```

## Overriding defaults
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/api_guard/authentication_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def create
end

def destroy
blacklist_token
revoke_token
render_success(message: I18n.t('api_guard.authentication.signed_out'))
end

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/api_guard/passwords_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def update
invalidate_old_jwt_tokens(current_resource)

if current_resource.update(password_params)
blacklist_token unless ApiGuard.invalidate_old_tokens_on_password_change
revoke_token unless ApiGuard.invalidate_old_tokens_on_password_change
destroy_all_refresh_tokens(current_resource)

create_token_and_set_header(current_resource, resource_name)
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/api_guard/tokens_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def create
create_token_and_set_header(current_resource, resource_name)

@refresh_token.destroy
blacklist_token if ApiGuard.blacklist_token_after_refreshing
revoke_token if ApiGuard.revoke_token_after_refreshing

render_success(message: I18n.t('api_guard.access_token.refreshed'))
end
Expand Down
4 changes: 2 additions & 2 deletions lib/api_guard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ module Test
mattr_accessor :invalidate_old_tokens_on_password_change
self.invalidate_old_tokens_on_password_change = false

mattr_accessor :blacklist_token_after_refreshing
self.blacklist_token_after_refreshing = false
mattr_accessor :revoke_token_after_refreshing
self.revoke_token_after_refreshing = false

mattr_accessor :api_guard_associations
self.api_guard_associations = {}
Expand Down
4 changes: 2 additions & 2 deletions lib/api_guard/jwt_auth/authentication.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def define_current_resource_accessors(resource)
end

# Authenticate the resource with the '{{resource_name}}_id' in the decoded JWT token
# and also, check for valid issued at time and not blacklisted
# and also, check for valid issued at time and not revoked
#
# Also, set "current_{{resource_name}}" method and "@current_{{resource_name}}" instance variable
# for accessing the authenticated resource
Expand All @@ -77,7 +77,7 @@ def authenticate_token

resource = find_resource_from_token(@resource_name.classify.constantize)

if resource && valid_issued_at?(resource) && !blacklisted?(resource)
if resource && valid_issued_at?(resource) && !revoked?(resource)
define_current_resource_accessors(resource)
end
end
Expand Down
35 changes: 0 additions & 35 deletions lib/api_guard/jwt_auth/blacklist_token.rb

This file was deleted.

35 changes: 35 additions & 0 deletions lib/api_guard/jwt_auth/revoke_token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

module ApiGuard
module JwtAuth
# Common module for token revocation functionality
module RevokeToken
def revoked_token_association(resource)
resource.class.revoked_token_association
end

def token_revocation_enabled?(resource)
revoked_token_association(resource).present?
end

def revoked_tokens_for(resource)
revoked_token_association = revoked_token_association(resource)
resource.send(revoked_token_association)
end

# Returns whether the JWT token is revoked or not
def revoked?(resource)
return false unless token_revocation_enabled?(resource)

revoked_tokens_for(resource).exists?(token: @token)
end

# Revoke the current JWT token from future access
def revoke_token
return unless token_revocation_enabled?(current_resource)

revoked_tokens_for(current_resource).create(token: @token, expire_at: Time.at(@decoded_token[:exp]).utc)
end
end
end
end
8 changes: 4 additions & 4 deletions lib/api_guard/models/concerns.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ module Concerns
extend ActiveSupport::Concern

class_methods do
def api_guard_associations(refresh_token: nil, blacklisted_token: nil)
def api_guard_associations(refresh_token: nil, revoked_token: nil)
return if ApiGuard.api_guard_associations[name]

ApiGuard.api_guard_associations[name] = {}
ApiGuard.api_guard_associations[name][:refresh_token] = refresh_token
ApiGuard.api_guard_associations[name][:blacklisted_token] = blacklisted_token
ApiGuard.api_guard_associations[name][:revoked_token] = revoked_token
end

def refresh_token_association
ApiGuard.api_guard_associations.dig(name, :refresh_token)
end

def blacklisted_token_association
ApiGuard.api_guard_associations.dig(name, :blacklisted_token)
def revoked_token_association
ApiGuard.api_guard_associations.dig(name, :revoked_token)
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/api_guard/modules.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
require 'api_guard/jwt_auth/json_web_token'
require 'api_guard/jwt_auth/authentication'
require 'api_guard/jwt_auth/refresh_jwt_token'
require 'api_guard/jwt_auth/blacklist_token'
require 'api_guard/jwt_auth/revoke_token'
require 'api_guard/response_formatters/renderer'
require 'api_guard/models/concerns'

Expand All @@ -15,7 +15,7 @@ module Modules
include ApiGuard::JwtAuth::JsonWebToken
include ApiGuard::JwtAuth::Authentication
include ApiGuard::JwtAuth::RefreshJwtToken
include ApiGuard::JwtAuth::BlacklistToken
include ApiGuard::JwtAuth::RevokeToken
include ApiGuard::ResponseFormatters::Renderer
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class AuthenticationController < ApiGuard::AuthenticationController
# end

# def destroy
# blacklist_token
# revoke_token
# render_success(message: I18n.t('api_guard.authentication.signed_out'))
# end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class PasswordsController < ApiGuard::PasswordsController
# invalidate_old_jwt_tokens(current_resource)
#
# if current_resource.update_attributes(password_params)
# blacklist_token unless ApiGuard.invalidate_old_tokens_on_password_change
# revoke_token unless ApiGuard.invalidate_old_tokens_on_password_change
# destroy_all_refresh_tokens(current_resource)
#
# create_token_and_set_header(current_resource, resource_name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class TokensController < ApiGuard::TokensController
# create_token_and_set_header(current_resource, resource_name)
#
# @refresh_token.destroy
# blacklist_token if ApiGuard.blacklist_token_after_refreshing
# revoke_token if ApiGuard.revoke_token_after_refreshing
#
# render_success(message: I18n.t('api_guard.access_token.refreshed'))
# end
Expand Down
4 changes: 2 additions & 2 deletions lib/generators/api_guard/initializer/templates/initializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# Default: false
# config.invalidate_old_tokens_on_password_change = false

# Blacklist JWT access token after refreshing
# Revoke JWT access token after refreshing
# Default: false
# config.blacklist_token_after_refreshing = false
# config.revoke_token_after_refreshing = false
end
4 changes: 2 additions & 2 deletions spec/dummy/app/models/admin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
class Admin < ApplicationRecord
has_secure_password

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

# == Validations =====================================================================================================
validates :email, presence: true
validates :email, uniqueness: true, allow_blank: true

# == Relationships ===================================================================================================
has_many :refresh_tokens, dependent: :delete_all
has_many :blacklisted_tokens, dependent: :delete_all
has_many :revoked_tokens, dependent: :delete_all
end
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

class BlacklistedToken < ApplicationRecord
class RevokedToken < ApplicationRecord
belongs_to :user, optional: true
belongs_to :admin, optional: true
end
4 changes: 2 additions & 2 deletions spec/dummy/app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
class User < ApplicationRecord
has_secure_password

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

# == Validations =====================================================================================================
validates :email, presence: true
validates :email, uniqueness: true, allow_blank: true

# == Relationships ===================================================================================================
has_many :refresh_tokens, dependent: :delete_all
has_many :blacklisted_tokens, dependent: :delete_all
has_many :revoked_tokens, dependent: :delete_all
has_many :posts, dependent: :delete_all
end
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# frozen_string_literal: true

class CreateBlacklistedTokens < ActiveRecord::Migration[5.1]
class CreateRevokedTokens < ActiveRecord::Migration[5.1]
def change
create_table :blacklisted_tokens do |t|
create_table :revoked_tokens do |t|
t.string :token
t.datetime :expire_at
t.references :user, foreign_key: true
Expand Down
Loading
Loading