Giter Club home page Giter Club logo

api_guard's Introduction

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:

$ bundle install

Or install it yourself as:

$ 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 has_secure_password 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 has_secure_password 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 has_secure_password. You can use has_secure_password 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

  # Validity of the refresh token
  # Default: 2 weeks
  # config.refresh_token_validity = 2.weeks

  # 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".

Refresh token validity

By default, the validity of the refresh token is 2 weeks from the creation. Override this by configuring refresh_token_validity

config.refresh_token_validity = 6.hours # Set six hours validity for refresh tokens

On accessing the refresh token API with expired refresh token, API Guard will respond 401 (Unauthenticated) with message "Invalid refresh token".

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 expire_at:datetime

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:

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:

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.

api_guard's People

Contributors

afaraldo avatar cristianstu avatar dependabot[bot] avatar gokul595 avatar mrchampz avatar rcarter avatar swrobel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

api_guard's Issues

Is there any way to implement HTTP Only Cookies?

Implemented this gem for JWT auth and it works like a charm, even with GraphQL.

However, using localstorage to store the Bearer token isn't very secure. Using HTTP Only Cookies seem to be a reasonable solution to that security issue. However, it seems that api_guard only supports the use of HTTP Headers to retrieve the tokens (including the refresh tokens)

Would it be possible to override the controllers to implement support for using HTTP Only cookies to retrieve the bearer and refresh tokens?

ActionCable support?

Hey, I'ma tryin to use this gem to auth ActionCable connection, but I cant manage it to work.
I'm kinda fresh user of rails websockets so I dont know if I'm doing all right but I think I do.
Also I'm doing own sort of auth as I made it with #38 to allow guest connections aswell.

on connection.rb I included helper that reads JWT (from cookies or params, doesnt matter here) and I pass it successfully in any possible way but after that I'm getting error.

There was an exception - NameError(undefined local variable or method decode_token' for #ApplicationCable::Connection:xxxxxxxxxxxx`

And I did try include ApiGuard and ApiGuard:JwAuth etc for helper, or for connection.rb, nothing worked

Example code of connection.rb that should work if decode_token would be defined

module ApplicationCable
  class Connection < ActionCable::Connection::Base
    # include ApiGuard::JwtAuth
    # include ApplicationHelper
    identified_by :current_user

    def connect
      # puts cookie_jwt_auth
      self.current_user = jwt_auth
    end

    private

    def jwt_auth
      @token = request.params[:token]
      @user = nil
      if @token
        begin
          user = decode_token
          puts user
          @user = User.find(user['user_id'])
        rescue Exception => e
          raise Exception, "JWT Token is not valid or user don't exist"
        end
      end
    end
  end
end

Avoid creating new refresh token when retrieving new access token

Currently refreshing an access token with a refresh token will create a new refresh token.

# frozen_string_literal: true

require_dependency 'api_guard/application_controller'

module ApiGuard
  class TokensController < ApplicationController
    before_action :authenticate_resource, only: [:create]
    before_action :find_refresh_token, only: [:create]

    def create
      create_token_and_set_header(current_resource, resource_name)

      @refresh_token.destroy
      blacklist_token if ApiGuard.blacklist_token_after_refreshing

      render_success(message: I18n.t('api_guard.access_token.refreshed'))
    end

This means if a user has their refresh token leaked, the refresh token can be used forever.
Correct me if I'm wrong, but the correct behavior should be just letting the refresh tokens expire.
Refreshing an access token should not renew the refresh token.

Could we extend to use public/private keys?

Hey - brilliant gem - was initially looking at Doorkeeper but was extremely pleased to find this one as its precisely what we needed.

I do have a use case I wanted to ask a question about, though. We need to authenticate a partner on a machine-to-machine type basis and my thinking was the best way to do this would be using a public/private key. I notice it says

Currently API Guard supports only HS256 algorithm for cryptographic signing

on the readme, but wanted to know if there was any material reason such a security, non compatibility with some of the code? I'm hoping it's more that you haven't had the need to implement it?

Having had a poke around at the code it looks (potentially) as simple as extending this class: https://github.com/Gokul595/api_guard/blob/66937cc56aaa62af987751e4b95fc673ab82a7fd/lib/api_guard/jwt_auth/json_web_token.rb - is that right?

Thanks in advance!

authenticate_and_set_user without restricting access

I'm using with my projects GraphQL, recently implemented your api_guard. Honestly its pretty good but I dont see option to like authenticate user but despite that user is logged allow to pass through just leaving current user nil.
It's needed to me as all graphql requests passes through one controller that passes user info to all queries. When I'm logged in all works well but in other way it dont stopping on unauthorized error on all queries.

before action error

before_action: athenticate_and_set_user gives undefined method error
Screenshot from 2019-03-26 10-03-05

full error log


Started POST "/advertisements" for 127.0.0.1 at 2019-03-26 10:08:05 +0545
Processing by AdvertisementsController#create as */*
  Parameters: {"title"=>"mobile for sale", "description"=>"as good asnew s7 for sale"}
Completed 500 Internal Server Error in 42ms (ActiveRecord: 0.0ms)


  
NoMethodError (undefined method `authenticate_or_request_with_http_token' for #<AdvertisementsController:0x00005647fc6b77f0>
Did you mean?  authenticate_and_set_resource):
  
api_guard (0.1.2) lib/api_guard/jwt_auth/authentication.rb:13:in `method_missing'
api_guard (0.1.2) lib/api_guard/jwt_auth/authentication.rb:21:in `authenticate_and_set_resource'
api_guard (0.1.2) lib/api_guard/jwt_auth/authentication.rb:11:in `method_missing'
activesupport (5.2.2.1) lib/active_support/callbacks.rb:426:in `block in make_lambda'
activesupport (5.2.2.1) lib/active_support/callbacks.rb:198:in `block (2 levels) in halting'
actionpack (5.2.2.1) lib/abstract_controller/callbacks.rb:34:in `block (2 levels) in <module:Callbacks>'
activesupport (5.2.2.1) lib/active_support/callbacks.rb:199:in `block in halting'
activesupport (5.2.2.1) lib/active_support/callbacks.rb:513:in `block in invoke_before'
activesupport (5.2.2.1) lib/active_support/callbacks.rb:513:in `each'
activesupport (5.2.2.1) lib/active_support/callbacks.rb:513:in `invoke_before'
activesupport (5.2.2.1) lib/active_support/callbacks.rb:131:in `run_callbacks'
actionpack (5.2.2.1) lib/abstract_controller/callbacks.rb:41:in `process_action'
actionpack (5.2.2.1) lib/action_controller/metal/rescue.rb:22:in `process_action'
actionpack (5.2.2.1) lib/action_controller/metal/instrumentation.rb:34:in `block in process_action'
activesupport (5.2.2.1) lib/active_support/notifications.rb:168:in `block in instrument'
activesupport (5.2.2.1) lib/active_support/notifications/instrumenter.rb:23:in `instrument'
activesupport (5.2.2.1) lib/active_support/notifications.rb:168:in `instrument'
actionpack (5.2.2.1) lib/action_controller/metal/instrumentation.rb:32:in `process_action'
actionpack (5.2.2.1) lib/action_controller/metal/params_wrapper.rb:256:in `process_action'
activerecord (5.2.2.1) lib/active_record/railties/controller_runtime.rb:24:in `process_action'
actionpack (5.2.2.1) lib/abstract_controller/base.rb:134:in `process'
actionpack (5.2.2.1) lib/action_controller/metal.rb:191:in `dispatch'
actionpack (5.2.2.1) lib/action_controller/metal.rb:252:in `dispatch'
actionpack (5.2.2.1) lib/action_dispatch/routing/route_set.rb:52:in `dispatch'
actionpack (5.2.2.1) lib/action_dispatch/routing/route_set.rb:34:in `serve'
actionpack (5.2.2.1) lib/action_dispatch/journey/router.rb:52:in `block in serve'
actionpack (5.2.2.1) lib/action_dispatch/journey/router.rb:35:in `each'
actionpack (5.2.2.1) lib/action_dispatch/journey/router.rb:35:in `serve'
actionpack (5.2.2.1) lib/action_dispatch/routing/route_set.rb:840:in `call'
rack (2.0.6) lib/rack/etag.rb:25:in `call'
rack (2.0.6) lib/rack/conditional_get.rb:38:in `call'
rack (2.0.6) lib/rack/head.rb:12:in `call'
activerecord (5.2.2.1) lib/active_record/migration.rb:559:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/callbacks.rb:28:in `block in call'
activesupport (5.2.2.1) lib/active_support/callbacks.rb:98:in `run_callbacks'
actionpack (5.2.2.1) lib/action_dispatch/middleware/callbacks.rb:26:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/executor.rb:14:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/debug_exceptions.rb:61:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
railties (5.2.2.1) lib/rails/rack/logger.rb:38:in `call_app'
railties (5.2.2.1) lib/rails/rack/logger.rb:26:in `block in call'
activesupport (5.2.2.1) lib/active_support/tagged_logging.rb:71:in `block in tagged'
activesupport (5.2.2.1) lib/active_support/tagged_logging.rb:28:in `tagged'
activesupport (5.2.2.1) lib/active_support/tagged_logging.rb:71:in `tagged'
railties (5.2.2.1) lib/rails/rack/logger.rb:26:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/request_id.rb:27:in `call'
rack (2.0.6) lib/rack/runtime.rb:22:in `call'
activesupport (5.2.2.1) lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/executor.rb:14:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/static.rb:127:in `call'
rack (2.0.6) lib/rack/sendfile.rb:111:in `call'
rack-cors (1.0.3) lib/rack/cors.rb:95:in `call'
railties (5.2.2.1) lib/rails/engine.rb:524:in `call'
puma (3.12.1) lib/puma/configuration.rb:227:in `call'
puma (3.12.1) lib/puma/server.rb:660:in `handle_request'
puma (3.12.1) lib/puma/server.rb:474:in `process_client'
puma (3.12.1) lib/puma/server.rb:334:in `block in run'
puma (3.12.1) lib/puma/thread_pool.rb:135:in `block in spawn_thread'

422 error, Unprocessable Entity, InvalidAuthenticityToken when trying to sign_up

Hi Gokul,

I followed your tutorial and tried to integrate api_guard on my rails api on an existing User model.

Everything goes well regarding the installation but when trying to create my first user with postman :

POST http://localhost:3000/users/sign_up
{
"email": "[email protected]",
"password": "test",
"password_confirmation": "test",
"birthdate": "Mar, 28 Mar 1991",
"description": "Web Developer",
"profilePictureUrl": "https://www.docker.com/sites/default/files/social/docker_facebook_share.png",
"inscriptionDate": "Mar, 28 Mar 1991",
"firstname": "Canavaggio",
"lastname": "Lorenzo"
}

I get this error :

{
    "status": 422,
    "error": "Unprocessable Entity",
    "exception": "#<ActionController::InvalidAuthenticityToken: ActionController::InvalidAuthenticityToken>",
    "traces": {
        "Application Trace": [],
        "Framework Trace": [
            {
                "id": 0,
                "trace": "actionpack (5.2.3) lib/action_controller/metal/request_forgery_protection.rb:211:in `handle_unverified_request'"
            },
...

So I guess the endpoint is correctly mapped but I may have missed something nor i'm not experienced enough to understand the issue. Could you help me out ?

Error when use nested routes like /api/v1/api_guard

Hi,
I try to use this gem with this route setup:

`
Rails.application.routes.draw do

Rails.application.routes.draw do

namespace :api do

  namespace :v1 do

    api_guard_routes for: 'users'

But when I try to use any resource I get this error:

<ActionController::RoutingError: uninitialized constant Api::V1::ApiGuard. Object.const_get(camel_cased_word). raise MissingController.new(error.message, error.name)

How can I do to use with gem this nested routes?

Thanks,

Is this project still maintained

Hi,

I have been using api_guard for many years and love it!

Starting a new project and of course reach for api_guard, but noticed it hasn't received any updates in 2 years, and seems like a risk to start a new project on a potentially unmaintained project.

Are you looking for contributors and/or actively maintaining this project?

Ability to turn off routes / feature such as deleting accounts

Currently, I'd like to be able to use the api_guard gem to do the user authentication. However, I do not want users to be able to delete their accounts.

Is there anyway to configure this through the initializer? Or do I have to override the RegistrationController and use an empty def destroy action to prevent users from deleting their accounts?

Or should I be removing the routes dynamically in an initializer?

POST data to sign_up doubt

Hello, I'll appreciate your help, I'm trying to use sign_up but I can't write anything but nil type.

#Front-end request

const signUp = async data => {
  console.log(data);
  const apiUrl = 'http://localhost:3002/users/sign_up';
  const response = await fetch(apiUrl, {
    method: 'POST',
    body: JSON.stringify(data),
  });
  return response.json();
};

RESPONSE:
//What I'm sending
Object { email: "[email protected]", name: "jessica", password: "password", password_confirmation: "password" }
loginRequest.js:2
//response
Object { status: "success", message: "Signed up successfully" }
//Database
#<User id: 2, name: nil, email: nil, password_digest: nil, created_at: "2021-03-14 18:56:47", updated_at: "2021-03-14 18:56:47">,

mongoid support

Hi,

Can it support Mongoid?

undefined method refresh_token_association' for User:Class (NoMethodError) /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/api_guard-0.1.3/lib/api_guard/route_mapper.rb:48:in generate_routes'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/api_guard-0.1.3/lib/api_guard/route_mapper.rb:18:in block (2 levels) in api_guard_routes' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/mapper.rb:879:in scope'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/api_guard-0.1.3/lib/api_guard/route_mapper.rb:17:in block in api_guard_routes' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/api_guard-0.1.3/lib/api_guard/route_mapper.rb:32:in block in api_guard_scope'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/mapper.rb:1008:in block in constraints' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/mapper.rb:879:in scope'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/mapper.rb:1008:in constraints' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/api_guard-0.1.3/lib/api_guard/route_mapper.rb:31:in api_guard_scope'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/api_guard-0.1.3/lib/api_guard/route_mapper.rb:16:in api_guard_routes' /srv/http/example.com/config/routes.rb:2:in block in

'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/route_set.rb:432:in instance_exec' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/route_set.rb:432:in eval_block'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/route_set.rb:414:in draw' /srv/http/example.com/config/routes.rb:1:in '
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:54:in load' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:54:in load'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:285:in block in load' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:257:in load_dependency'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:285:in load' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/application/routes_reloader.rb:41:in block in load_paths'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/application/routes_reloader.rb:41:in each' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/application/routes_reloader.rb:41:in load_paths'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/application/routes_reloader.rb:20:in reload!' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/application/routes_reloader.rb:30:in block in updater'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-5.2.3/lib/active_support/file_update_checker.rb:83:in execute' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/application/routes_reloader.rb:10:in execute'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/application/finisher.rb:130:in block in <module:Finisher>' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/initializable.rb:32:in instance_exec'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/initializable.rb:32:in run' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/initializable.rb:61:in block in run_initializers'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/2.6.0/tsort.rb:228:in block in tsort_each' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/2.6.0/tsort.rb:350:in block (2 levels) in each_strongly_connected_component'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/2.6.0/tsort.rb:431:in each_strongly_connected_component_from' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/2.6.0/tsort.rb:349:in block in each_strongly_connected_component'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/2.6.0/tsort.rb:347:in each' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/2.6.0/tsort.rb:347:in call'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/2.6.0/tsort.rb:347:in each_strongly_connected_component' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/2.6.0/tsort.rb:226:in tsort_each'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/2.6.0/tsort.rb:205:in tsort_each' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/initializable.rb:60:in run_initializers'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/application.rb:361:in initialize!' /srv/http/example.com/config/environment.rb:5:in <top (required)>'
config.ru:3:in require_relative' config.ru:3:in block in '
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/rack-2.0.7/lib/rack/builder.rb:55:in instance_eval' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/rack-2.0.7/lib/rack/builder.rb:55:in initialize'
config.ru:1:in new' config.ru:1:in '
/usr/local/Cellar/passenger/6.0.2_2/libexec/src/helper-scripts/rack-preloader.rb:101:in eval' /usr/local/Cellar/passenger/6.0.2_2/libexec/src/helper-scripts/rack-preloader.rb:101:in preload_app'
/usr/local/Cellar/passenger/6.0.2_2/libexec/src/helper-scripts/rack-preloader.rb:189:in block in <module:App>' /usr/local/Cellar/passenger/6.0.2_2/libexec/src/ruby_supportlib/phusion_passenger/loader_shared_helpers.rb:380:in run_block_and_record_step_progress'
/usr/local/Cellar/passenger/6.0.2_2/libexec/src/helper-scripts/rack-preloader.rb:188:in <module:App>' /usr/local/Cellar/passenger/6.0.2_2/libexec/src/helper-scripts/rack-preloader.rb:30:in module:PhusionPassenger'
/usr/local/Cellar/passenger/6.0.2_2/libexec/src/helper-scripts/rack-preloader.rb:29:in `'
The stdout/stderr output of the subprocess so far is:

Error: The application encountered the following error: undefined method refresh_token_association' for User:Class (NoMethodError) /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/api_guard-0.1.3/lib/api_guard/route_mapper.rb:48:in generate_routes'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/api_guard-0.1.3/lib/api_guard/route_mapper.rb:18:in block (2 levels) in api_guard_routes' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/mapper.rb:879:in scope'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/api_guard-0.1.3/lib/api_guard/route_mapper.rb:17:in block in api_guard_routes' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/api_guard-0.1.3/lib/api_guard/route_mapper.rb:32:in block in api_guard_scope'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/mapper.rb:1008:in block in constraints' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/mapper.rb:879:in scope'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/mapper.rb:1008:in constraints' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/api_guard-0.1.3/lib/api_guard/route_mapper.rb:31:in api_guard_scope'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/api_guard-0.1.3/lib/api_guard/route_mapper.rb:16:in api_guard_routes' /srv/http/example.com/config/routes.rb:2:in block in

'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/route_set.rb:432:in instance_exec' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/route_set.rb:432:in eval_block'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/route_set.rb:414:in draw' /srv/http/example.com/config/routes.rb:1:in '
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:54:in load' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:54:in load'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:285:in block in load' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:257:in load_dependency'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:285:in load' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/application/routes_reloader.rb:41:in block in load_paths'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/application/routes_reloader.rb:41:in each' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/application/routes_reloader.rb:41:in load_paths'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/application/routes_reloader.rb:20:in reload!' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/application/routes_reloader.rb:30:in block in updater'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-5.2.3/lib/active_support/file_update_checker.rb:83:in execute' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/application/routes_reloader.rb:10:in execute'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/application/finisher.rb:130:in block in <module:Finisher>' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/initializable.rb:32:in instance_exec'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/initializable.rb:32:in run' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/initializable.rb:61:in block in run_initializers'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/2.6.0/tsort.rb:228:in block in tsort_each' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/2.6.0/tsort.rb:350:in block (2 levels) in each_strongly_connected_component'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/2.6.0/tsort.rb:431:in each_strongly_connected_component_from' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/2.6.0/tsort.rb:349:in block in each_strongly_connected_component'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/2.6.0/tsort.rb:347:in each' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/2.6.0/tsort.rb:347:in call'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/2.6.0/tsort.rb:347:in each_strongly_connected_component' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/2.6.0/tsort.rb:226:in tsort_each'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/2.6.0/tsort.rb:205:in tsort_each' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/initializable.rb:60:in run_initializers'
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-5.2.3/lib/rails/application.rb:361:in initialize!' /srv/http/example.com/config/environment.rb:5:in <top (required)>'
config.ru:3:in require_relative' config.ru:3:in block in '
/srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/rack-2.0.7/lib/rack/builder.rb:55:in instance_eval' /srv/http/example.com/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/rack-2.0.7/lib/rack/builder.rb:55:in initialize'
config.ru:1:in new' config.ru:1:in '
/usr/local/Cellar/passenger/6.0.2_2/libexec/src/helper-scripts/rack-preloader.rb:101:in eval' /usr/local/Cellar/passenger/6.0.2_2/libexec/src/helper-scripts/rack-preloader.rb:101:in preload_app'
/usr/local/Cellar/passenger/6.0.2_2/libexec/src/helper-scripts/rack-preloader.rb:189:in block in <module:App>' /usr/local/Cellar/passenger/6.0.2_2/libexec/src/ruby_supportlib/phusion_passenger/loader_shared_helpers.rb:380:in run_block_and_record_step_progress'
/usr/local/Cellar/passenger/6.0.2_2/libexec/src/helper-scripts/rack-preloader.rb:188:in <module:App>' /usr/local/Cellar/passenger/6.0.2_2/libexec/src/helper-scripts/rack-preloader.rb:30:in module:PhusionPassenger'
/usr/local/Cellar/passenger/6.0.2_2/libexec/src/helper-scripts/rack-preloader.rb:29:in `'

I can also get:

undefined method `api_guard_associations' for User:Class (NoMethodError)

Unauthorized error

I get " unauthorized " status response, from the " authenticate_and_set_user" method. I have the Access token in my request header.

Screenshot from 2019-03-27 09-42-46

The response.

Screenshot from 2019-03-27 09-52-58

Error log

Started POST "/advertisements" for 127.0.0.1 at 2019-03-27 09:42:41 +0545
Processing by AdvertisementsController#create as */*
  Parameters: {"title"=>"mobile for sale", "description"=>"as good asnew s7 for sale"}
Filter chain halted as :authenticate_and_set_user rendered or redirected
Completed 401 Unauthorized in 1ms (Views: 0.2ms | ActiveRecord: 0.0ms)

How to send a new access token to the browser before the old access token expire?

Hi,
I have an API service app with rails.
I'm getting Access-Token and Refresh-Token Once I had sign_in.
Every request I'm sending Authorization and Refresh-Token. Here I want to new access token before the current access token expire.
In the current situation automatically I'm getting Access token expired.

How can I auto-renew a new access token?

TypeError: no implicit conversion of nil into String

I get " TypeError: no implicit conversion of nil into string " error when I try to register a new user.

Screenshot from 2019-03-18 12-46-47

Here is my log:

Started POST "/users/sign_up" for 127.0.0.1 at 2019-03-18 12:46:42 +0545
   (0.1ms)  SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
  ↳ /var/lib/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
Processing by ApiGuard::RegistrationController#create as */*
  Parameters: {"email"=>"[email protected]", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}
   (0.1ms)  begin transaction
  ↳ /var/lib/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
  User Create (0.2ms)  INSERT INTO "users" ("email", "password_digest", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["email", "[email protected]"], ["password_digest", "$2a$10$0jckG96Q3WUPczek.EoRhuJfVM853aIzBwk/1tw3NUG7.l78tYYji"], ["created_at", "2019-03-18 07:01:42.286906"], ["updated_at", "2019-03-18 07:01:42.286906"]]
  ↳ /var/lib/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
   (107.6ms)  commit transaction
  ↳ /var/lib/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
Completed 500 Internal Server Error in 175ms (ActiveRecord: 108.6ms)


  
TypeError (no implicit conversion of nil into String):
  
jwt (2.1.0) lib/jwt/algos/hmac.rb:14:in `digest'
jwt (2.1.0) lib/jwt/algos/hmac.rb:14:in `sign'
jwt (2.1.0) lib/jwt/signature.rb:35:in `sign'
jwt (2.1.0) lib/jwt/encode.rb:39:in `encoded_signature'
jwt (2.1.0) lib/jwt/encode.rb:47:in `encode_segments'
jwt (2.1.0) lib/jwt/encode.rb:20:in `initialize'
jwt (2.1.0) lib/jwt.rb:21:in `new'
jwt (2.1.0) lib/jwt.rb:21:in `encode'
api_guard (0.1.1) lib/api_guard/jwt_auth/json_web_token.rb:21:in `encode'
api_guard (0.1.1) lib/api_guard/jwt_auth/json_web_token.rb:36:in `jwt_and_refresh_token'
api_guard (0.1.1) lib/api_guard/jwt_auth/json_web_token.rb:47:in `create_token_and_set_header'
api_guard (0.1.1) app/controllers/api_guard/registration_controller.rb:10:in `create'
actionpack (5.2.2.1) lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action'
actionpack (5.2.2.1) lib/abstract_controller/base.rb:194:in `process_action'
actionpack (5.2.2.1) lib/action_controller/metal/rendering.rb:30:in `process_action'
actionpack (5.2.2.1) lib/abstract_controller/callbacks.rb:42:in `block in process_action'
activesupport (5.2.2.1) lib/active_support/callbacks.rb:132:in `run_callbacks'
actionpack (5.2.2.1) lib/abstract_controller/callbacks.rb:41:in `process_action'
actionpack (5.2.2.1) lib/action_controller/metal/rescue.rb:22:in `process_action'
actionpack (5.2.2.1) lib/action_controller/metal/instrumentation.rb:34:in `block in process_action'
activesupport (5.2.2.1) lib/active_support/notifications.rb:168:in `block in instrument'
activesupport (5.2.2.1) lib/active_support/notifications/instrumenter.rb:23:in `instrument'
activesupport (5.2.2.1) lib/active_support/notifications.rb:168:in `instrument'
actionpack (5.2.2.1) lib/action_controller/metal/instrumentation.rb:32:in `process_action'
actionpack (5.2.2.1) lib/action_controller/metal/params_wrapper.rb:256:in `process_action'
activerecord (5.2.2.1) lib/active_record/railties/controller_runtime.rb:24:in `process_action'
actionpack (5.2.2.1) lib/abstract_controller/base.rb:134:in `process'
actionview (5.2.2.1) lib/action_view/rendering.rb:32:in `process'
actionpack (5.2.2.1) lib/action_controller/metal.rb:191:in `dispatch'
actionpack (5.2.2.1) lib/action_controller/metal.rb:252:in `dispatch'
actionpack (5.2.2.1) lib/action_dispatch/routing/route_set.rb:52:in `dispatch'
actionpack (5.2.2.1) lib/action_dispatch/routing/route_set.rb:34:in `serve'
actionpack (5.2.2.1) lib/action_dispatch/routing/mapper.rb:18:in `block in <class:Constraints>'
actionpack (5.2.2.1) lib/action_dispatch/routing/mapper.rb:48:in `serve'
actionpack (5.2.2.1) lib/action_dispatch/journey/router.rb:52:in `block in serve'
actionpack (5.2.2.1) lib/action_dispatch/journey/router.rb:35:in `each'
actionpack (5.2.2.1) lib/action_dispatch/journey/router.rb:35:in `serve'
actionpack (5.2.2.1) lib/action_dispatch/routing/route_set.rb:840:in `call'
rack (2.0.6) lib/rack/etag.rb:25:in `call'
rack (2.0.6) lib/rack/conditional_get.rb:38:in `call'
rack (2.0.6) lib/rack/head.rb:12:in `call'
activerecord (5.2.2.1) lib/active_record/migration.rb:559:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/callbacks.rb:28:in `block in call'
activesupport (5.2.2.1) lib/active_support/callbacks.rb:98:in `run_callbacks'
actionpack (5.2.2.1) lib/action_dispatch/middleware/callbacks.rb:26:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/executor.rb:14:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/debug_exceptions.rb:61:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
railties (5.2.2.1) lib/rails/rack/logger.rb:38:in `call_app'
railties (5.2.2.1) lib/rails/rack/logger.rb:26:in `block in call'
activesupport (5.2.2.1) lib/active_support/tagged_logging.rb:71:in `block in tagged'
activesupport (5.2.2.1) lib/active_support/tagged_logging.rb:28:in `tagged'
activesupport (5.2.2.1) lib/active_support/tagged_logging.rb:71:in `tagged'
railties (5.2.2.1) lib/rails/rack/logger.rb:26:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/request_id.rb:27:in `call'
rack (2.0.6) lib/rack/runtime.rb:22:in `call'
activesupport (5.2.2.1) lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/executor.rb:14:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/static.rb:127:in `call'
rack (2.0.6) lib/rack/sendfile.rb:111:in `call'
railties (5.2.2.1) lib/rails/engine.rb:524:in `call'
puma (3.12.0) lib/puma/configuration.rb:225:in `call'
puma (3.12.0) lib/puma/server.rb:658:in `handle_request'
puma (3.12.0) lib/puma/server.rb:472:in `process_client'
puma (3.12.0) lib/puma/server.rb:332:in `block in run'
puma (3.12.0) lib/puma/thread_pool.rb:133:in `block in spawn_thread'

These are my files:

user.rb

class User < ApplicationRecord
has_secure_password
end

routes.rb

Rails.application.routes.draw do
  api_guard_routes for: 'users'
end

gemfile

source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.5.1'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.2.2', '>= 5.2.2.1'
# Use sqlite3 as the database for Active Record
gem 'sqlite3', '~>1.3.6'
# Use Puma as the app server
gem 'puma', '~> 3.11'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
# gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use ActiveModel has_secure_password
 gem 'bcrypt', '~> 3.1.7'
gem 'api_guard'

# Use ActiveStorage variant
# gem 'mini_magick', '~> 4.8'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.1.0', require: false

# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
 gem 'rack-cors'

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end

group :development do
  gem 'listen', '>= 3.0.5', '< 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end


# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

[Question] How to set a cookie in authentication response?

I'm building a hybrid app with Turbo and want to set the session cookie when signing in via the API. The client will then pass this cookie to the web view to sign in there.

I've wired up my custom controller via routes but am running into an issue when using #sign_in from Devise. Here's my controller, the only line that is different is the call to #sign_in.

module Users
  class AuthenticationController < ApiGuard::AuthenticationController
    before_action :find_resource, only: [:create]

    def create
      if resource.authenticate(params[:password])
        create_token_and_set_header(resource, resource_name)
        sign_in("User", resource) # This line is the only change.
        render_success(message: I18n.t("api_guard.authentication.signed_in"))
      else
        render_error(422, message: I18n.t("api_guard.authentication.invalid_login_credentials"))
      end
    end

    private

    def find_resource
      self.resource = resource_class.find_by(email: params[:email].downcase.strip) if params[:email].present?
      render_error(422, message: I18n.t("api_guard.authentication.invalid_login_credentials")) unless resource
    end
  end
end

Successfully authenticating raises the following error:

RuntimeError (Could not find a valid mapping for #<User id: 1, email: "[email protected]", created_at: "2021-03-14 04:34:15.068574000 +0000", updated_at: "2021-03-17 00:03:05.959170000 +0000">):

app/controllers/users/authentication_controller.rb:8:in `create'

Is it not possible to use Devise methods here? I feel like I'm missing something obvious and any help would be greatly appreciated!

Guest access without bearer token

I would like a configuration option allowing to have a guest access. If no bearer token was sent, then authenticate_and_set_user should just return without any error and current_user would be left as nil. The line to change is this: https://github.com/Gokul595/api_guard/blob/master/lib/api_guard/jwt_auth/authentication.rb#L28

I managed myself to create a different before_action based on authenticate_and_set_resources doing exactly what I want, but a configuration for letting both behaviours would be great. Tell me if you need this in your gem, I will do a PR.

Rails 6.1 undefined method 'day' for 1:Integer

Describe the bug
undefined method 'day' for 1:Integer error is raised while running one of the following commands :

  • rails t
  • rails c
  • rails s

Gem version (please complete the following information):

  • Ruby: 2.7.3
  • Rails 6.1.4
  • API Guard 0.5.1

Add posibility to refresh expired access token

Problem:
If access jwt token expired we can't refresh token no more. So there is no point in making refresh token with much longer lifespan.
My idea:
How about just using refresh token? I think there is no point in blacklisting them then.

My code example:
Bilanuk@b66920d
image

Issue with a custom logout route

Hello,

In my project, I need a custom logout route.
Here is my SessionsController including that route:

module Api
    class SessionsController < Api::BaseController
        def destroy
            authenticate_and_set_user
            blacklist_token
            render json: { success: true }
        end
    end
end

I can see in the log that the user is loaded, then the token is blacklisted but just after that, for whatever reason, I get JWT::InvalidJtiError (Missing jti) which raise a 500 error. I don't know why i get that, any idea ?

Thanks a lot for this awesome lib

Undefined method error when tyring to refresh tokens

Screenshot from 2019-03-27 09-38-56

full error log

Started POST "/users/tokens" for 127.0.0.1 at 2019-03-27 09:38:06 +0545
Processing by ApiGuard::TokensController#create as */*
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 10], ["LIMIT", 1]]
  ↳ /var/lib/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
Completed 500 Internal Server Error in 10ms (ActiveRecord: 0.2ms)


  
NoMethodError (undefined method `blacklisted_tokens' for #<User:0x00007f144823d850>):
  
activemodel (5.2.2.1) lib/active_model/attribute_methods.rb:430:in `method_missing'
api_guard (0.1.3) lib/api_guard/jwt_auth/blacklist_token.rb:15:in `blacklisted_tokens_for'
api_guard (0.1.3) lib/api_guard/jwt_auth/blacklist_token.rb:21:in `blacklisted?'
api_guard (0.1.3) lib/api_guard/jwt_auth/authentication.rb:65:in `authenticate_token'
api_guard (0.1.3) lib/api_guard/jwt_auth/authentication.rb:24:in `authenticate_and_set_resource'
api_guard (0.1.3) lib/api_guard/jwt_auth/authentication.rb:11:in `method_missing'
api_guard (0.1.3) app/controllers/api_guard/application_controller.rb:4:in `public_send'
api_guard (0.1.3) app/controllers/api_guard/application_controller.rb:4:in `authenticate_resource'
activesupport (5.2.2.1) lib/active_support/callbacks.rb:426:in `block in make_lambda'
activesupport (5.2.2.1) lib/active_support/callbacks.rb:179:in `block (2 levels) in halting_and_conditional'
actionpack (5.2.2.1) lib/abstract_controller/callbacks.rb:34:in `block (2 levels) in <module:Callbacks>'
activesupport (5.2.2.1) lib/active_support/callbacks.rb:180:in `block in halting_and_conditional'
activesupport (5.2.2.1) lib/active_support/callbacks.rb:513:in `block in invoke_before'
activesupport (5.2.2.1) lib/active_support/callbacks.rb:513:in `each'
activesupport (5.2.2.1) lib/active_support/callbacks.rb:513:in `invoke_before'
activesupport (5.2.2.1) lib/active_support/callbacks.rb:131:in `run_callbacks'
actionpack (5.2.2.1) lib/abstract_controller/callbacks.rb:41:in `process_action'
actionpack (5.2.2.1) lib/action_controller/metal/rescue.rb:22:in `process_action'
actionpack (5.2.2.1) lib/action_controller/metal/instrumentation.rb:34:in `block in process_action'
activesupport (5.2.2.1) lib/active_support/notifications.rb:168:in `block in instrument'
activesupport (5.2.2.1) lib/active_support/notifications/instrumenter.rb:23:in `instrument'
activesupport (5.2.2.1) lib/active_support/notifications.rb:168:in `instrument'
actionpack (5.2.2.1) lib/action_controller/metal/instrumentation.rb:32:in `process_action'
actionpack (5.2.2.1) lib/action_controller/metal/params_wrapper.rb:256:in `process_action'
activerecord (5.2.2.1) lib/active_record/railties/controller_runtime.rb:24:in `process_action'
actionpack (5.2.2.1) lib/abstract_controller/base.rb:134:in `process'
actionview (5.2.2.1) lib/action_view/rendering.rb:32:in `process'
actionpack (5.2.2.1) lib/action_controller/metal.rb:191:in `dispatch'
actionpack (5.2.2.1) lib/action_controller/metal.rb:252:in `dispatch'
actionpack (5.2.2.1) lib/action_dispatch/routing/route_set.rb:52:in `dispatch'
actionpack (5.2.2.1) lib/action_dispatch/routing/route_set.rb:34:in `serve'
actionpack (5.2.2.1) lib/action_dispatch/routing/mapper.rb:18:in `block in <class:Constraints>'
actionpack (5.2.2.1) lib/action_dispatch/routing/mapper.rb:48:in `serve'
actionpack (5.2.2.1) lib/action_dispatch/journey/router.rb:52:in `block in serve'
actionpack (5.2.2.1) lib/action_dispatch/journey/router.rb:35:in `each'
actionpack (5.2.2.1) lib/action_dispatch/journey/router.rb:35:in `serve'
actionpack (5.2.2.1) lib/action_dispatch/routing/route_set.rb:840:in `call'
rack (2.0.6) lib/rack/etag.rb:25:in `call'
rack (2.0.6) lib/rack/conditional_get.rb:38:in `call'
rack (2.0.6) lib/rack/head.rb:12:in `call'
activerecord (5.2.2.1) lib/active_record/migration.rb:559:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/callbacks.rb:28:in `block in call'
activesupport (5.2.2.1) lib/active_support/callbacks.rb:98:in `run_callbacks'
actionpack (5.2.2.1) lib/action_dispatch/middleware/callbacks.rb:26:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/executor.rb:14:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/debug_exceptions.rb:61:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
railties (5.2.2.1) lib/rails/rack/logger.rb:38:in `call_app'
railties (5.2.2.1) lib/rails/rack/logger.rb:26:in `block in call'
activesupport (5.2.2.1) lib/active_support/tagged_logging.rb:71:in `block in tagged'
activesupport (5.2.2.1) lib/active_support/tagged_logging.rb:28:in `tagged'
activesupport (5.2.2.1) lib/active_support/tagged_logging.rb:71:in `tagged'
railties (5.2.2.1) lib/rails/rack/logger.rb:26:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/request_id.rb:27:in `call'
rack (2.0.6) lib/rack/runtime.rb:22:in `call'
activesupport (5.2.2.1) lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/executor.rb:14:in `call'
actionpack (5.2.2.1) lib/action_dispatch/middleware/static.rb:127:in `call'
rack (2.0.6) lib/rack/sendfile.rb:111:in `call'
rack-cors (1.0.3) lib/rack/cors.rb:95:in `call'
railties (5.2.2.1) lib/rails/engine.rb:524:in `call'
puma (3.12.1) lib/puma/configuration.rb:227:in `call'
puma (3.12.1) lib/puma/server.rb:660:in `handle_request'
puma (3.12.1) lib/puma/server.rb:474:in `process_client'
puma (3.12.1) lib/puma/server.rb:334:in `block in run'
puma (3.12.1) lib/puma/thread_pool.rb:135:in `block in spawn_thread'

refresh tokens is not set in the headers

hey, when I try to sign in or sign up, I suppose to get the access token and refresh token and the expiration, but I don't see when I sign in the refresh token, I just see the access token

Resource is shared with multiple requests(even unauthenticated ones)

The resource is shared by multiple requests of the same controller.
That is because when the current_<resource> method is created, it's created for all instances of the controller, and the resource loaded by one authentication is shared to all via the closure in define_current_resource_accessors:

def define_current_resource_accessors(resource)
self.class.send(:define_method, "current_#{@resource_name}") do
instance_variable_get("@current_#{@resource_name}") ||
instance_variable_set("@current_#{@resource_name}", resource)
end
end

How to reproduce

Add a hook to your controller for debug(that would run before authenticate_and_set_resource):

before_action :debug_resource
def debug_resource
  puts "My resource: #{respond_to?(:current_resource) ? current_resource : nil}"
end

Then send two requests:

  1. Send a request with a token of resource 1
  2. Send a request with no token at all((would return 401 Unauthorized)

Result: Second request had resource 1 in current_resource

Expectation

  1. current_resource should only contain the current request's authenticated resource, otherwise nil
  2. current_resource shouldn't change during the execution of a request - with this bug it might, due to a race condition

Gem version:

  • API Guard 0.5.0(latest)

Expiring Refresh Tokens

Is there a way to implement expiring refresh tokens? From the docs, it looks like refresh tokens are valid forever unless explicitly blacklisted (and therefore have to enable blacklisting)?

Most JWT implementations I have seen have a short expiry (eg 1 day) for the access token and a long-lived expiry (eg 200 days) for the refresh token.

This doesn't seem to be an option with this gem, unfortunately, unless I am missing something? Are there any solutions to cover the use case of implementing an expiring refresh token?

Ability to not only change password but also to reset forgotten password

Is your feature request related to a problem? Please describe.
As far as I understand the current features of this api_guard gem, there is no real functionality to reset a user password if it was forgotten. I would like to have such a feature, in my opinion its close to a must-have-feature.

Describe the solution you'd like
A tip on how to implement a password forgotten feature with the api_guard gem and devise.

Describe alternatives you've considered
Currently I am implementing a password reset feature, but I don't know if it is going to work.

Additional context
The change password feature is nice. But I would like to have the ability for users to reset their passwords before they are logged in. E.g. the user want's to login with his already created account. But before he does, he notices, that he has forgotten his password. After that, he clicks on the (self made) password forgotten Link and get's to a new View where he can enter his email. Then the user clicks submit. He receives an Email with a link + token to reset the password. Then a request with a body containing the users email and passwords gets sent and after that, the password has been resetted and all Tokens refreshed.

can still view controller action after sign out

Describe the bug
I have configured api_guard and I can sign in fine, get the token and use it to browse a controller action view but after I sign out successfully, I can still go to the same controller action and still view the erb output. I am doing all this in Postman.

Gem version (please complete the following information):

  • Ruby: 2.5.1p57
  • Rails 5.2.2
  • API Guard 0.2.2

How to map to Users::User?

Our app scopes our user model to Users::User. How can we configure API Guard's routes to work with this? (We are also using Devise.)

This seems to be the only combination of for:, as:, and path: options that doesn't raise an error when starting the server. But when POSTing to /api/users/sign_in I get the following error.

NameError (`@api_guard_Users::User' is not allowed as an instance variable name)

  # API Guard Routes
  scope path: :api do
    api_guard_routes for: "Users::User", as: "users", path: "users", controller: {
      authentication: "users/authentication"
    }
  end

  # Devise / Auth Routes
  devise_for :users, class_name: "Users::User", controllers: {
    sessions: "users/sessions",
    registrations: "users/registrations",
    masquerades: "users/admin/masquerades"
  }

I'm assuming (hoping!) this can be fixed in the route configuration. Any help would be greatly appreciated!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.