Apple Auth not working on Ruby on Rails Backend - ruby-on-rails

I need to authentificate user with Apple Auth in mobile application. I got IdentityToken, User and AuthCode from api request from mobile app.
I need to authentificate user and get its email. My class AppleId always returns: "invalid_client".
Maybe i have some mistakes in my logic?
class AppleId
URL = 'https://appleid.apple.com/auth/token'
ISSUER = 'https://appleid.apple.com'
TEAM_ID = '6Z******38'
KEY_ID = '33******KP'
IDENTIFIER = 'ru.k*************on'
PRIVATE_KEY = <<-PEM
-----BEGIN PRIVATE KEY-----
MIGTA**********z7
-----END PRIVATE KEY-----
PEM
def initialize
#private_key = OpenSSL::PKey::EC.new PRIVATE_KEY
end
def authenticate(code)
make_request(code)
end
private
def make_request(code)
uri = URI.parse(URL)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri)
params = {
client_id: IDENTIFIER,
client_secret: client_secret_jwt,
code: code,
grant_type: 'authorization_code'
}
request.set_form_data(params)
response = http.request(request)
response.body
end
def client_secret_jwt
jwt = JSON::JWT.new(
iss: TEAM_ID,
aud: ISSUER,
sub: IDENTIFIER,
iat: Time.now,
exp: 1.minutes.from_now
)
jwt.kid = KEY_ID
JWT.encode jwt, #private_key, 'ES256'
end
end
In my Controller:
authCode = params[:auth_code]
AppleId.new.authenticate(authCode)
=> "{\"error\":\"invalid_client\"}"

Related

How to generate query string hash with query parameters

I am sending API requests to JIRA using JWT token authentication. I added the method (get, post, etc.) and the endpoint to SHA256 encoding. This succeeds:
qsh = Digest::SHA256.hexdigest("GET&#{endpoint}&")
jwt = JWT.encode({
qsh: qsh,
iat: issued_at,
exp: expires_at,
iss: key
}, secret)
However, I cannot add query parameters to the URI. If I append query parameters:
qsh = Digest::SHA256.hexdigest("GET&#{endpoint}&start=50&limit=50")
jwt = JWT.encode({
qsh: qsh,
iat: issued_at,
exp: expires_at,
iss: key
}, secret)
I receive unauthorized response 401.
MVP:
jira_request(:get,"/rest/servicedeskapi/servicedesk/#{serviceDeskId}/organization", nil)
def jira_request(method, endpoint, data)
request = Typhoeus::Request.new(jira_rest_api_url(method, endpoint),
followlocation: true, method: method,
body: data ? data.to_json : nil,
headers: { 'X-ExperimentalApi' => 'opt-in',
'Content-Type' => 'application/json' })
request.on_complete do |response|
if response.success? && !response.body.blank?
return JSON.parse(response.body)
elsif response.code == 204
return true
else
return false
end
end
request.run
end
# Creating JWT token to Auth for each request
def jira_rest_api_url(method, endpoint)
# Gets the ADDON details for generating JWT token
jwt_auth = MyJToken.first
issued_at = Time.now.utc.to_i
expires_at = issued_at + 500
qsh = Digest::SHA256.hexdigest("#{method.to_s.upcase}&#{endpoint}&")
jwt = JWT.encode({ qsh: qsh,
iat: issued_at,
exp: expires_at,
iss: jwt_auth.key
}, jwt_auth.secret)
# return the service call URL with the JWT token added
"#{jwt_auth.api_base_url}#{endpoint}?jwt=#{jwt}"
end
end
The parameters that are hashed in:
qsh = Digest::SHA256.hexdigest("GET&#{endpoint}&limit=50&start=50")
should be added in the request url:
"#{jwt_auth.api_base_url}#{endpoint}?jwt=#{jwt}&start=50&limit=50"

Getting Unable to refresh google_oauth2 authentication token error.

I am trying to get access tokens from OAuth.io for any Google based provider however whenever I authenticate I get an access_token but no refresh_token. I have chosen offline for the access_type but still no joy.
def google_auth
# Create a new API client & load the Google Drive API
client = Google::APIClient.new
client.authorization.client_id = ENV['GOOGLE_ID']
client.authorization.client_secret = ENV['GOOGLE_SECRET']
client.authorization.scope = ENV['GOOGLE_SCOPE']
client.authorization.redirect_uri = ENV['REDIRECT_URI']
client.authorization.code = self.code.chomp || ""
client.authorization.access_token = self.token
client.authorization.refresh_token = self.refresh_token
# client.authorization.additional_parameters = {
# "access_type" => "offline", # offline access
# "include_granted_scopes" => "true" # incremental auth
#
if client.authorization.refresh_token &&
client.authorization.expired?
client.authorization.fetch_access_token!
end
return client
end
def refresh_google
options = {
body: {
client_id: ENV['GOOGLE_ID'],
client_secret: ENV['GOOGLE_SECRET'],
refresh_token: self.refresh_token,
grant_type: 'refresh_token',
access_type: 'offline'
},
headers: {
'Content-Type' => 'application/x-www-form-urlencoded',
# 'access_type' =>'offline'
}
}
#response = HTTParty.post('https://accounts.google.com/o/oauth2/token', options)
if #response.code == 200
self.token = #response.parsed_response['access_token']
self.save
else
Rails.logger.error("Unable to refresh google_oauth2 authentication token.")
Rails.logger.error("Refresh token response body: #{#response.body}")
end
end
Please help in this regard
Issue resolved: I was trying to put
"client.authorization.additional_parameters = {
"access_type" => "offline", # offline access
"include_granted_scopes" => "true" # incremental auth"
in User the rb model where callback was made but had to put it under session controller where API was called!

Net::HTTPBadResponse (wrong header line format)

Please help me understand, what i'm doing wrong?
I need to make POST request to api from my rails app, i tried to do it firstly with gem "faraday" and then with "net/http". Response from server the same. Account creates, but gives error:
Net::HTTPBadResponse (wrong header line format)
class Connect
require 'uri'
require 'net/http'
require 'net/https'
require 'faraday'
def initialize(email, password, account)
#base_url = "https://example.com"
#client_id = ENV["client_id"]
#client_secret = ENV["client_secret"]
#email = email
#password = password
#account = account
end
# method with net/http
def create_account
#toSend = {
first_name: #account[:first_name],
last_name: #account[:last_name],
client_id: #client_id,
client_secret: #client_secret,
email: #email,
password: #password
}.to_json
uri = URI.parse("#{#base_url}/endclient/api/account")
https = Net::HTTP.new(uri.host,uri.port)
https.use_ssl = true
req = Net::HTTP::Post.new(
uri.path,
initheader = {
'Content-Type' =>'application/json',
'Accept' =>'application/json'
}
)
req.body = "#{#toSend}"
res = https.request(req)
end
# faraday method
def make_account
conn = Faraday.new(:url => "#{#base_url}") do |faraday|
faraday.response :logger
faraday.adapter Faraday.default_adapter
end
params = { first_name: #account[:first_name],
last_name: #account[:last_name],
client_id: #client_id,
client_secret: #client_secret,
email: #email,
password: #password
}.to_json
response = conn.post do |req|
req.url '/endclient/api/account'
req.headers['Content-Type'] = 'application/json'
req.headers['Accept'] = 'application/json'
req.body = params
end
end
end
Done this request with success via Postman, so endpoint of the api is working.
Please help me understand what's wrong with headers ?
Thanks in advance!
UPDATE:
May be problem in this ?
Cannot render console with content type application/jsonAllowed content types: [#<Mime::Type:0x00000003661658 #synonyms=["application/xhtml+xml"], #symbol=:html, #string="text/html">, #<Mime::Type:0x00000003660fc8 #synonyms=[], #symbol=:text, #string="text/plain">, #<Mime::Type:0x0000000365ca68 #synonyms=[], #symbol=:url_encoded_form, #string="application/x-www-form-urlencoded">]
If you want a http lib that easily mixes into a class/module than I would recommend httparty over Faraday or just plain old Net::HTTP.
require 'httparty' # Requires go at the top of the file. Not in class body.
class MyApiClient
include HTTParty
base_uri 'test.example.com'
# Tells Httparty to send JSON for all requests.
format :json
# #see https://robots.thoughtbot.com/ruby-2-keyword-arguments
def initialize(client_id: ENV["client_id"], client_secret: ENV["client_secret"])
#options = {
body: {
client_id: client_id,
client_secret: client_secret
}
}
end
# #return [HTTParty::Response]
def create_account(first_name:, last_name:, email:, password:)
# ActiveSupport method that creates a copy so that we don't
# alter the instance options as a side effect.
opts = #options.deep_dup
opts[:body].merge!(
email: email,
password: password,
first_name: first_name,
last_name: last_name
)
self.class.post('/endclient/api/account', opts)
end
end
The nice thing is that httparty will do all the work with parsing the parameters to and from JSON and sending the correct headers. It also takes care of the dull work of building URI's.
Note that the method signature is slightly different:
client = MyApiClient.new
response = client.create_account(
first_name: 'John',
last_name: 'Doe',
email: 'john.doe#example.com',
password: 'abcd1234'
)
There was a problem on API side, wrong symbols in header.

Twitter Access Token URL returns Invalid Request Token

In my controller I have the following actions
def twitter
client = TwitterOAuth::Client.new(
:consumer_key => ENV['TWITTER_KEY'],
:consumer_secret => ENV['TWITTER_SECRET']
)
request_token = client.request_token(oauth_callback: "http://myawesomeapp.herokuapp.com/create_users/get_twitter_info")
redirect_to "https://www.twitter.com/oauth/authenticate?oauth_token=#{request_token.params[:oauth_token]}"
end
def get_twitter_info
redirect_to "https://www.twitter.com/oauth/access_token?oauth_verifier=#{params[:oauth_verifier]}&oauth_token=#{params[:oauth_token]}"
end
def results
end
In my Twitter application settings, I've set Access to Read Only and checkmarked Allow this application to be used to Sign in with Twitter. Why is it returning
Invalid Request Token
when it hits the https://twitter.com/oauth/access_token page?
In Main function You write Simply this code
redirect_url = "http://myawesomeapp.herokuapp.com/create_users/get_twitter_info"
url = URI.parse(URI.encode(redirect_url.strip))
consumer_key=consumer key
consumer_secret=consumer secret
oauth = OAuth::Consumer.new(consumer_key, consumer_secret,
{ :site => "http://api.twitter.com" })
request_token = oauth.get_request_token(:oauth_callback => url)
session[:twitter_token] = request_token.token
session[:twitter_secret] = request_token.secret
and in
get_twitter_info
function you write
consumer_key=consumer key
consumer_secret=consumer secret
oauth = OAuth::Consumer.new(consumer_key,consumer_secret,
{ :site => "http://api.twitter.com" })
request_token = OAuth::RequestToken.new(oauth, session[:twitter_token], session[:twitter_secret])
#access_token = request_token.get_access_token(
:oauth_verifier => params[:oauth_verifier])
But Keep in mind in your twitter application setting you mention callback url is
http://myawesomeapp.herokuapp.com/create_users/get_twitter_info

Google OAuth access tokens

I'm so confused by OAuth and Google. It took me forever to get the refresh_token to create a new access_token. Then to find out the refresh_token expires too?? What is the point of that!!!??
All I need to do is persist a valid access_token for use with legato.
Here is what I manually enter into my terminal to retrieve an OAUTH code:
client = OAuth2::Client.new('GA_CLIENT_ID', 'GA_SECRET_KEY', {
:authorize_url => 'https://accounts.google.com/o/oauth2/auth',
:token_url => 'https://accounts.google.com/o/oauth2/token'
})
client.auth_code.authorize_url({
:scope => 'https://www.googleapis.com/auth/analytics.readonly',
:redirect_uri => 'http://localhost',
:access_type => 'offline',
:approval_prompt=> 'force'
})
Then I manually enter the outputted url to in my browser. I export the returned OAUTH code as to an env variable and get the access token:
access_token = client.auth_code.get_token(ENV['GA_OAUTH_CODE'], :redirect_uri => 'http://localhost')
Then I can access the access_token and refresh_token:
begin
api_client_obj = OAuth2::Client.new(ENV['GA_CLIENT_ID'], ENV['GA_SECRET_KEY'], {:site => 'https://www.googleapis.com'})
api_access_token_obj = OAuth2::AccessToken.new(api_client_obj, ENV['GA_OAUTH_ACCESS_TOKEN'])
self.user = Legato::User.new(api_access_token_obj)
self.user.web_properties.first # this tests the access code and throws an exception if invalid
rescue Exception => e
refresh_token
end
end
def refresh_token
refresh_client_obj = OAuth2::Client.new(ENV['GA_CLIENT_ID'], ENV['GA_SECRET_KEY'], {
:authorize_url => 'https://accounts.google.com/o/oauth2/auth',
:token_url => 'https://accounts.google.com/o/oauth2/token'
})
refresh_access_token_obj = OAuth2::AccessToken.new(refresh_client_obj, ENV['GA_OAUTH_ACCESS_TOKEN'], {refresh_token: ENV['GA_OAUTH_REFRESH_TOKEN']})
refresh_access_token_obj.refresh!
self.user = Legato::User.new(refresh_access_token_obj)
end
After an hour, my tokens expire and I have to manually start the process over again from the browser! How can I replicate this in code??
Here you go, made a little something just for you :)
It's a simple implementation, specifically to ease the pain of renewing tokens.
Just be sure to:
Put in your own APP_ID and APP_SECRET.
Either only save your refresh_token and call refresh_token() every time before you use it, or use refresh_token_if_needed() every time, and re-save the token and expires_at (preferred obviously , since you'll only refresh when needed).
Let me know how it worked out.
.
require 'gmail'
require 'gmail_xoauth'
require 'httparty'
class GmailManager
APP_ID = "DDDDDDDDDDDD-SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS.apps.googleusercontent.com"
APP_SECRET = "SSSSSS-SSSSSSSSSSSSSSSSS"
def refresh_token(refresh_token)
Rails.logger.info "[GmailManager:refresh_token] refreshing using this refresh_token: #{refresh_token}"
# Refresh auth token from google_oauth2 and then requeue the job.
options = {
body: {
client_id: APP_ID,
client_secret: APP_SECRET,
refresh_token: refresh_token,
grant_type: 'refresh_token'
},
headers: {
'Content-Type' => 'application/x-www-form-urlencoded'
}
}
response = HTTParty.post('https://accounts.google.com/o/oauth2/token', options)
if response.code == 200
token = response.parsed_response['access_token']
expires_in = DateTime.now + response.parsed_response['expires_in'].seconds
Rails.logger.info "Success! token: #{token}, expires_in #{expires_in}"
return token, expires_in
else
Rails.logger.error "Unable to refresh google_oauth2 authentication token."
Rails.logger.error "Refresh token response body: #{response.body}"
end
return nil, nil
end
def refresh_token_if_needed(token, expires_on, refresh_token)
if token.nil? or expires_on.nil? or Time.now >= expires_on
Rails.logger.info "[GmailManager:refresh_token_if_needed] refreshing using this refresh_token: #{refresh_token}"
new_token, new_expires_on = self.refresh_token(refresh_token)
if !new_token.nil? and !new_expires_on.nil?
return new_token, new_expires_on
end
else
Rails.logger.info "[GmailManager:refresh_token_if_needed] not refreshing. using this token: #{token}"
end
return token, expires_on
end
end

Resources