Repeated authorization gives error 422 with Doorkeeper (resource owner account stream)

I am new to Rails and webdev. Trying to implement a simple API for a mobile application with Rails + Devise + Doorkeeper (e.g. https://github.com/doorkeeper-gem/doorkeeper-provider-app ).

Faced a problem that the user cannot fulfill the authorization request (POST / oauth / token) if he has already received the token. I.e:.

curl -F grant_type=password -F username=1@tothetrip.com -F password=12345678 -X POST http://api.to_the_trip.dev/oauth/token 

First time:

 {"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7ImlkIjoyLCJlbWFpbCI6IjFAdG90aGV0cmlwLmNvbSJ9fQ.dYai6nH_KYb9YbDltqwFuzCO3i0igR_gw2T7u_TeVcI","token_type":"bearer","expires_in":7200,"created_at":1435864812} 

The token goes into the oauth_access_tokens table (which is not necessary for JWT, but not a problem).

If I repeat this request, I get a 422 page of errors and rails with something like

  ActiveRecord::RecordInvalid in Doorkeeper::TokensController#create Validation failed: Token has already been taken activerecord (4.2.3) lib/active_record/validations.rb:79:in `raise_record_invalid' activerecord (4.2.3) lib/active_record/validations.rb:43:in `save!' activerecord (4.2.3) lib/active_record/attribute_methods/dirty.rb:29:in `save!' activerecord (4.2.3) lib/active_record/transactions.rb:291:in `block in save!' activerecord (4.2.3) lib/active_record/transactions.rb:351:in `block in with_transaction_returning_status' activerecord (4.2.3) lib/active_record/connection_adapters/abstract/database_statements.rb:213:in `block in transaction' activerecord (4.2.3) lib/active_record/connection_adapters/abstract/transaction.rb:184:in `within_new_transaction' activerecord (4.2.3) lib/active_record/connection_adapters/abstract/database_statements.rb:213:in `transaction' activerecord (4.2.3) lib/active_record/transactions.rb:220:in `transaction' activerecord (4.2.3) lib/active_record/transactions.rb:348:in `with_transaction_returning_status' activerecord (4.2.3) lib/active_record/transactions.rb:291:in `save!' activerecord (4.2.3) lib/active_record/persistence.rb:51:in `create!' doorkeeper (2.2.1) lib/doorkeeper/models/access_token_mixin.rb:76:in `find_or_create_for' doorkeeper (2.2.1) lib/doorkeeper/oauth/request_concern.rb:33:in `find_or_create_access_token' doorkeeper (2.2.1) lib/doorkeeper/oauth/password_access_token_request.rb:30:in `before_successful_response' doorkeeper (2.2.1) lib/doorkeeper/oauth/request_concern.rb:7:in `authorize' doorkeeper (2.2.1) lib/doorkeeper/request/password.rb:19:in `authorize' doorkeeper (2.2.1) app/controllers/doorkeeper/tokens_controller.rb:42:in `authorize_response' doorkeeper (2.2.1) app/controllers/doorkeeper/tokens_controller.rb:4:in `create' 

Even if I cancel the token with POST / oauth / revoke, everything will be the same, except for canceling the timestamp in oauth_access_tokens. And this is very strange.

I will investigate it a bit and find the code fragment in the gatekeeper jewel (access_token_mixin.rb):

 def find_or_create_for(application, resource_owner_id, scopes, expires_in, use_refresh_token) if Doorkeeper.configuration.reuse_access_token access_token = matching_token_for(application, resource_owner_id, scopes) if access_token && !access_token.expired? return access_token end end create!( application_id: application.try(:id), resource_owner_id: resource_owner_id, scopes: scopes.to_s, expires_in: expires_in, use_refresh_token: use_refresh_token ) end 

So, the error in the creation! a method that says that we tried to add a duplicate (in stacktrace). And if I set reuse_access_token in Doorkeeper.configure, then this is normal. But I get the same token after each permission, which is very unsafe, as I understand it. And yes, if I manually remove the token from oauth_access_tokens, then I can auth.

So what's wrong?

My Doorkeeper Configuration:

 Doorkeeper.configure do # Change the ORM that doorkeeper will use. # Currently supported options are :active_record, :mongoid2, :mongoid3, # :mongoid4, :mongo_mapper orm :active_record resource_owner_authenticator do current_user || env['warden'].authenticate!(:scope => :user) end resource_owner_from_credentials do |routes| request.params[:user] = {:email => request.params[:username], :password => request.params[:password]} request.env["devise.allow_params_authentication"] = true user = request.env['warden'].authenticate!(:scope => :user) env['warden'].logout user end access_token_generator "Doorkeeper::JWT" end Doorkeeper.configuration.token_grant_types << "password" Doorkeeper::JWT.configure do #JWT config end 

Routes

 require 'api_constraints' Rails.application.routes.draw do use_doorkeeper devise_for :users namespace :api, defaults: {format: :json}, constraints: { subdomain: 'api' }, path: '/' do scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do resources :users, :only => [:show, :create, :update] get '/me' => "credentials#me" end end end 
+5
source share
2 answers

Well, if you want to find the answer, then just formulate the question.

The problem was the default implementation of Dokereeper :: JWT. It has no randomness in the payload, so it has always been the same for each user authentication. So I added:

 Doorkeeper::JWT.configure do token_payload do |opts| user = User.find(opts[:resource_owner_id]) { iss: "myapp", #this iat: DateTime.current.utc.to_i, #this rnd: SecureRandom.hex, #and this user: { id: user.id, email: user.email } } end secret_key "key" encryption_method :hs256 end 

And it works great.

+7
source

I do not have enough reputation to comment on the selected answer, so I will add another answer to suggest an improvement.

Instead of creating an rnd claim that is prone to name conflicts, use the jti reserved claim as it is intended to provide a unique identifier for the JWT. I also recommend using UUID instead of Hex for jti value.

 Doorkeeper::JWT.configure do token_payload do |opts| user = User.find(opts[:resource_owner_id]) { iss: "myapp", iat: DateTime.current.utc.to_i, jti: SecureRandom.uuid, user: { id: user.id, email: user.email } } end secret_key "key" encryption_method :hs256 end 

Here you can find out more about the JWT claims filed.

+5
source

All Articles