HTTPBadRequest {"error_description": "Code expired", "error": "invalid_grant"} for Oauth authentication, Ruby on Rails
I have strange behavior in my omniauth app. Basically, I have an admin panel that requires authentication with a Yandex account.
Problem . I did everything that was required in several tutorials and everything went fine since yesterday I was trying to authenticate with a Yandex account and I got an HTTPBadRequest error.
Note . I haven't changed the code in my code. All my client_Id access details and password have not changed either.
Gemfile:
gem "omniauth-yandex"
Routes
devise_for :users, :controllers => { :omniauth_callbacks => "callbacks" }
CallbacksController:
def yandex
require 'net/http'
require 'json' # => false
@user = User.from_omniauth(request.env["omniauth.auth"])
@client_id = Rails.application.secrets.client_id
@secret = Rails.application.secrets.password
@authorization_code = params[:code]
@user.update_attribute(:code, @authorization_code)
@user.update_attribute(:state, params[:state])
@post_body = "grant_type=authorization_code&code=#{@authorization_code}&client_id=#{@client_id}&client_secret=#{@secret}"
@url = "https://oauth.yandex.ru/token"
url = URI.parse(@url)
req = Net::HTTP::Post.new(url.request_uri)
req['host'] ="oauth.yandex.ru"
req['Content-Length'] = @post_body.length
req['Content-Type'] = 'application/x-www-form-urlencoded'
req.body = @post_body
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = (url.scheme == "https")
@response_mess = http.request(req)
refreshhash = JSON.parse(@response_mess.body)
access_token = refreshhash['access_token']
refresh_token = refreshhash['refresh_token']
access_token_expires_at = DateTime.now + refreshhash["expires_in"].to_i.seconds
if access_token.present? && refresh_token.present? && access_token_expires_at.present?
@user.update_attribute(:access_token, access_token)
@user.update_attribute(:refresh_token, refresh_token)
@user.update_attribute(:expires_in, access_token_expires_at)
sign_in(@user)
redirect_to admin_dashboard_index_path
end
end
User model:
require 'rest-client'
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:omniauthable, :omniauth_providers => [:yandex]
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email
user.code = auth.info.code
user.state = auth.info.state
user.password = Devise.friendly_token[0,20]
end
end
def refresh_token_if_expired
if token_expired?
response = RestClient.post "https://oauth.yandex.com/token",
:grant_type => 'refresh_token',
:refresh_token => self.refresh_token
refreshhash = JSON.parse(response.body)
self.access_token = refreshhash['access_token']
self.expires_in = DateTime.now + refreshhash["expires_in"].to_i.seconds
self.save
puts 'Saved'
end
end
def token_expired?
expiry = Time.at(self.expires_in)
logger.debug "#{expiry}"
return true if expiry < Time.now
token_expires_at = expiry
save if changed?
false
end
end
Problem :
HTTPBadRequest
the error highlights the line in CallbacksController
:
@response_mess = http.request(req)
What I have tried :
1) Restarted Rails application;
2) Removed user from database and tried to login again;
3) Removed registered application in the Yandex Oauth section . Then added a new client_id and password again.
From Yandex Oauth manual :
Exchanging an authorization code for a token
The application sends the code, along with its ID and password, in a POST request.
POST /token HTTP/1.1
Host: oauth.yandex.
Content-type: application/x-www-form-urlencoded
Content-Length: <length of request body>
[Authorization: Basic <encoded client_id:client_secret string>]
grant_type=authorization_code
& code=<authorization code>
[& client_id=<application ID>]
[& client_secret=<application password>]
[& device_id=<device ID>]
[& device_name=<device name>]
UPDATE : I am getting this error:
{"error_description": "Code has expired", "error": "invalid_grant"}
UPDATE 2 . I tried to contact Yandex support. I sent them a short description of my problem and a link to the question. But there is no answer.
UPDATE 3:
I tried the same POST request on CHROME POSTMAN and got the same response as
{"error_description": "Code expired", "error": "invalid_grant"}
Update 4:
I checked all my client_id and password for Yandex again and they are 100% valid.
source to share
From OAUTH 2.0 RFC :
invalid_grant
The provided authorization grant (e.g., authorization
code, resource owner credentials) or refresh token is
invalid, expired, revoked, does not match the redirection
URI used in the authorization request, or was issued to
another client.
This is explained further on the Yandex website :
Failed to access access_token. Either the temporary authorization code was not issued by Yandex.Money, or it has expired, or an access_token for this temporary authorization code has already been issued (a duplicate request for an access token using the same temporary authorization code).
You are sending invalid credentials to Yandex. Forget the code you wrote for a moment, because the only faulty part is when it sends data to Yandex. Yandex informs that the data is invalid, and you confirmed it using Postman. This means that this is sending the wrong data:
@post_body = "grant_type=authorization_code&code=#{@authorization_code}&client_id=#{@client_id}&client_secret=#{@secret}"
So, double check these things:
- Is it supposed
grant_type
authorization_code
? - Are you sure you are
@authorization_code
set to a value (and not zero) and that this value is valid? - Same question for
@client_id
? - The same question
@secret
? - Are you missing the meaning
redirect_uri
?
source to share