Computing the JWT signature for Google OAuth Service Account using erlang? - erlang

I have created google service account and have JSON file containing private_key, client_email etc.
JWT should be created to get access token.
I have followed following step
Header computation:
Header = jsx:encode(#{<<"alg">> => <<"RS256">>,<<"typ">> => <<"JWT">>}).
Base64Header = base64:encode(Header).
Claims computation:
Claims = jsx:encode(#{
<<"iss">> => <<"google-123#some-test.iam.gserviceaccount.com">>,
<<"scope">> => <<"https://www.googleapis.com/auth/cloud-platform">>,
<<"aud">> => <<"https://www.googleapis.com/oauth2/v4/token">>,
<<"exp">> => 1471629262,
<<"iat">> => 1471627282
}).
Base64Claims = base64:encode(Claims).
Input = {Base64Header}.{Base64Claim}
And,
How we can sign the UTF-8 representation of the Input using SHA256withRSA (also known as RSASSA-PKCS1-V1_5-SIGN with the SHA-256 hash function) with the private_key to compute JWT Signature?

There are libraries already built to do this. One (which I am using) is Erlang JOSE.
%% In OTP 17 or later
Signed = jose_jwt:sign(RSAPrivate, #{ <<"alg">> => <<"RS256">> }, Payload),
{_JWS, Token} = jose_jws:compact(Signed).

Please have a look at https://github.com/kivra/oauth2_client
As of version 1.4.0 it supports authorization with a Service Account credentials JSON file, there is a pull request I created with the example https://github.com/kivra/oauth2_client/pull/26.

Related

Saml_IDP gem not pulling metadata or acceptable_hosts

I'm trying to implement SSO via SAML and using my application as both the IdP (routes are based off localhost:3000) and SP (routes based off lvh.me:3000). I used the saml_idp gem to set up the IdP and the omniauth-saml gem for the SP side.
I can get a request sent to the IdP and a response sent back if I bypass the validate_saml_request method in the SamlIdpController in the gem. The response I get returns the user's email and first/last name per what I configured in the config/saml_idp.rb file.
I really don't want to bypass the validate_saml_request. When I looked to see why my request is not being validated, I see that the logs return '[] compare to lvh.me' and 'No acceptable AssertionConsumerServiceURL, either configure them via config.service_provider.response_hosts or match to your metadata_url_host'.
For SPs, my config/saml_idp.rb file looks like:
service_providers = {
"my-application" => {
fingerprint: ENV['SAML_CERT_FINGERPRINT'],
metadata_url: "http://lvh.me:3000/auth/saml/metadata",
response_hosts: ["lvh.me:3000"]
},
}
I confirmed that the metadata does exist at that metadata_url as well. Not really sure why it's not pulling anything via the metadata_url or response_hosts via the config.
On my SP side, the config/omniauth.rb file looks like:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :saml,
:assertion_consumer_service_url => "http://lvh.me:3000/auth/saml/callback",
:issuer => "portal",
:idp_sso_target_url => "http://localhost:3000/saml/auth",
:idp_sso_target_url_runtime_params => {:original_request_param => :mapped_idp_param},
:idp_cert => ENV['SAML_CERT'],
:idp_cert_fingerprint => ENV['SAML_CERT_FINGERPRINT'],
:idp_cert_fingerprint_validator => lambda { |fingerprint| fingerprint },
:name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
end
While I was unable to get it working using this piece of configuration in the config/saml_idp.rb file, I only needed to have the IdP interact with one SP and was able to get it working by adjusting what I passed to response_hosts and using another part of the code given in the config file (example below):
config.service_provider.finder = ->(issuer_or_entity_id) do
{
fingerprint: ENV['SAML_CERT_FINGERPRINT'],
metadata_url: "http://lvh.me:3000/saml/metadata",
response_hosts: ["lvh.me"]
}
end

rails google-api-client contact api

I want to get access to a user's contact list with the google contacts API.
I've managed to get the token and refresh token and I'm now trying to use then on my rails server.
The google-api-client gem seems to be the way to go but I could not find which discovered_api to use. Greg Baugues provides a great tuto to get the gmail API working. The general request seems to look like
client = Google::APIClient.new
client.authorization.access_token = user_token
service = client.discovered_api('gmail')
result = client.execute(
:api_method => service.users.labels.list,
:parameters => {'userId' => 'me'},
:headers => {'Content-Type' => 'application/json'})
pp JSON.parse(result.body)
But I could not find how to query it for contacts. Running
client.discovered_apis.each do |gapi|
puts "#{gapi.title} \t #{gapi.id} \t #{gapi.preferred} \n"
end
(from here) shows now API related to contacts and I'm wondering if this is implemented in the alpha version of the gem...
As mentioned by #abraham, the Google contact API is not supported by the discovery API. Here is how I did it in ruby from the access token using the gems google_contacts_api and oauth2 (thanks to Rael Gugelmin Cunha for pointing them to me):
client = OAuth2::Client.new(client_id, client_secret, site: url)
token = OAuth2::AccessToken.new(client, access_token)
google_contacts_user = GoogleContactsApi::User.new(token)
contacts = google_contacts_user.contacts
There might be some more elegant way to do it but this works :)
The Google Contacts API is on Google's older GData API standard and is not supported by the discovery API. There is a pretty extensive guide for plain Ruby and a helper gem. Retrieving all contacts doesn't provide a Ruby sample but the Python sample should translate pretty easily.
def PrintAllContacts(gd_client):
feed = gd_client.GetContacts()
for i, entry in enumerate(feed.entry):
print '\n%s %s' % (i+1, entry.name.full_name.text)
if entry.content:
print ' %s' % (entry.content.text)
# Display the primary email address for the contact.
for email in entry.email:
if email.primary and email.primary == 'true':
print ' %s' % (email.address)

Cannot get oAuth2 Access Token for Google Analytics API

I am using Rails + Garb Gem (Sija Branch) + omniauth-google-oauth2 Gem and I can successfully authenticate with the Google Analytics API and extract data that our app is generating when using a user login, e.g.:
Garb::Session.login('USERNAME', '<PASSWORD>')
I can then use Garb to connect to the Analytics Profile I want and pull the data from it and display some charts on a webpage. This all works fine.
However, I want to use oAuth2 to authenticate with Analytics which is why I had to install the Sija branch of the Garb Gem from Github (it supports oAuth2) and I also installed the omniauth-google-oauth2 Gem. Now in theory I should be able to authenticate using the following code:
Garb::Session.access_token = access_token # an instance of OAuth2::Client
It's at this point that it gets a little hazy for me and I would greatly appreciate some guidance. Here's how far I have gotten:
1) I created a Project in the Google API console and turned on Analytics API under Services
2) This provided me with a Client ID and Client Secret
3) I came across this code which I could populate with the ID and Secret above:
client = OAuth2::Client.new(
GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET,
{
:site => 'https://accounts.google.com',
:authorize_url => '/o/oauth2/auth',
:token_url => '/o/oauth2/token'
})
4) Then there is the next bit of code:
response = OAuth2::AccessToken.new(
client,
STORED_TOKEN, {
refresh_token: STORED_REFRESH_TOKEN,
expires_at: STORED_EXPIRES_AT
})
5) and then in theory connect with:
Garb::Session.access_token = response
The problem I have is I don't have the token information in Point (4) above. It seems to me that with oAuth2 I need to do a "handshake" once and print out the return token values? Perhaps through Rails code which prints the values returned out and then paste the token values into a constant in the Rails app so that I can use them in the above code? I really am confused. As I mentioned earlier, the web app works fine using the user login authentication. All the web app is doing is authenticating with analytics, pulling down some data and drawing a chart. But I am stuck converting it over to oAuth2 as I just do not know how to get the Access Token that the Garb Gem is looking for. I should also note that this is not a public website with multiple users authenticating, this is a CMS website that is connecting to our own Analytics data.
I have seen some partial snippets of aspects of this but not a fully explained or working example. I would really appreciate any guidance and help with this question.
Many thanks in advance,
JR
I've soldiered through this over the last few weeks, so let me share what worked:
To use Oauth2 you need to get a 'refresh token' that you use to 're-authenticate' with google each time you make an API call. The steps for this are as follows:
1) Setup your account in the API console - https://code.google.com/apis/console/b/0/ (seems like you've done that well)
2) In your API account, make sure you have a redirect URI pointing back to your application:
http://some-url.com/auth/google_oauth2/callback
http://localhost:3000/auth/google_oauth2/callback
Note here that google won't let you call back to your local machine as 0.0.0.0:3000... so you'll need to use localhost explicitly
3) In your route file, tie that redirect url to an action in the controller where you're going to create the project or authentication
match '/auth/:provider/callback' => 'authentications#create'
The ':provider' simply lets you match on multiple types of oauth, but you could just put 'google_oauth2' there as well.
4) Now create that action in your controller
def create
auth = request.env["omniauth.auth"]
params = request.env["omniauth.params"]
project = Project.find(params['project_id'])
Authentication.create(:project_id => project.id, :provider => auth['provider'], :uid => auth['uid'], :access_token => auth['credentials']['refresh_token'])
flash[:notice] = "Authentication successful."
redirect_to owner_view_project_path(project)
end
5) The controller action should retrieve the relevant fields from the response object (details of response object here: https://github.com/zquestz/omniauth-google-oauth2) - in particular, you need to get the 'refresh_token' and save that to your project or authentication object - if you haven't added an 'access_token' attribute to the desired object, go do that now with a migration, then start saving the refresh token to that attribute
6) Now when you're ready to call that particular authentication and get API data for it, you can load up that object where you saved the access token, and use that to get a new session with the google API as follows:
#authentication = Authentications.find(params[:id])
client = OAuth2::Client.new GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET,
{
:site => 'https://accounts.google.com',
:authorize_url => "/o/oauth2/auth",
:token_url => "/o/oauth2/token",
}
response = OAuth2::AccessToken.from_hash(client, :refresh_token => #authentication.access_token).refresh!
Garb::Session.access_token = response
#profiles = Garb::Management::Profile.all
What this code did was create an OAuth2 access token (response) by specifying the client and then a refresh_token, then calling 'refresh!' to get a refreshed access token... then use that access token to establish your Garb session, then call down all the profiles for a given account using the Gard::Management::Profile.all
Hope this helps - let me know if you have questions!
Just a note on what worked for me in:
For steps 3, 4 & 5 I used cURL instead to retrieve the Access/Refresh token. Step 6 is then the same for me (using the Sija branch of the Garb Gem). So using cURL:
Using the details associated with your Google app POST the following using cURL:
curl --data "code=<APP_CODE>&redirect_uri=http://localhost:3000/oauth2callback&client_id=<CLIENT_ID>.apps.googleusercontent.com&scope=&client_secret=<CLIENT_SECRET>&grant_type=authorization_code" https://accounts.google.com/o/oauth2/token
The response takes the form:
{
"access_token" : "<ACCESS_TOKEN>",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "<REFRESH_TOKEN>"
}
which you can plug into the Garb Gem as per part 6.
The answer by #CamNorgate is valid.
If you don't have a "refresh_token" back from Omniauth on the callback make sure you are correctly initializing :google_oauth2
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2, ENV["GOOGLE_CLIENT_ID"], ENV["GOOGLE_CLIENT_SECRET"],
{ :scope=>"https://www.google.com/m8/feeds, https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile",
:approval_prompt=>"force", access_type="offline"
}
end
Make sure to include :approval_prompt=>"force", access_type="offline" in order for the refresh_token to be sent back. The refresh_token is only provided on the first authorization from the user.

Google-api-client asking for access token

My requirement is to get top 20 links for a search query in google.com.
I am using the Google-api-client! for ruby.
Here goes the code I am using,
require 'google/api_client'
client = Google::APIClient.new
response = client.execute(
search.cse.list, 'key' => '<My Key>', 'cx' => '013036536707430787589%3A_pqjad5hr1a', 'alt' => 'json', 'q' => 'hello world'
)
Now I am facing three problems,
I want to use default Google search, so what should be the 'cx' value? One which I used, is from https://developers.google.com/custom-search/v1/using_rest#cx
I am getting no results, instead getting the following warning "ArgumentError: Missing access token." I solved this issue using a dummy token, by defining "client.authorization.access_token = '123'" . But I am not sure, if it is a correct solution or not.
After I define the access_token, still I am getting no result. Instead getting the warning "Invalid Credentials". But if I use the same URL(generated by the api), in the browser I am getting results.
Instead of setting a dummy access token, just set the authorization mechanism to nil:
client.authorization = nil
This way it won't send an authorization header and will just rely on the API key for identifying your app.

Twitter Streaming API with OAuth?

I've been stuck on this for awhile. Does anyone know how to authenticate the Twitter Streaming API requests using OAuth? As of right now I'm authenticating via Basic Authentication, and I would like to completely switch over to OAuth. Also, I'm using Ruby on Rails if that helps.
Thanks
Connecting to the Twitter Streaming API via OAuth is done much the same as connecting via the REST API. Assuming you've already negotiated an access token, you sign and issue the request using the same signing algorithm as for a REST request. With the Streaming API, it's best to use header-based OAuth rather than query-string based.
Here's an example of a signed OAuth-based request for the sample end point:
GET http://stream.twitter.com/1/statuses/sample.json
Signature Base String example:
GET&http%3A%2F%2Fstream.twitter.com%2Fstatuses%2Fsample.json&oauth_consumer_key%3Dri8JxYK2ddwSV5xIUfNNvQ%26oauth_nonce%3DUJb0f3nHhFQkpkWkJzxnFT65xX1TZeuGjww6Q2XWs4%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1306947138%26oauth_token%3D819797-torCkTs0XK7H2Y2i1ee5iofqkMC4p7aayeEXRTmlw%26oauth_version%3D1.0
Authorization Header after signing:
Authorization: OAuth oauth_consumer_key="ri8JxYK2ddwSV5xIUfNNvQ", oauth_nonce="UJb0f3nHhFQkpkWkJzxnFT65xX1TZeuGjww6Q2XWs4", oauth_signature="bN14zlBIdCZCSl9%2B8UV8dB2VWjI%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1306947138", oauth_token="819797-torCkTs0XK7H2Y2i1ee5iofqkMC4p7aayeEXRTmlw", oauth_version="1.0"
Matt Harris has some sample code in PHP demonstrating connecting to the streaming API via OAuth: https://github.com/themattharris/tmhOAuth/blob/master/examples/streaming.php
After you register your application on http://dev.twitter.com this is how it's done in Perl:
#!/usr/bin/perl
use strict;
use AnyEvent::Twitter::Stream;
if ($ENV{FIREHOSE_SERVER}) {
$AnyEvent::Twitter::Stream::STREAMING_SERVER = $ENV{FIREHOSE_SERVER};
}
my $done = AE::cv;
binmode STDOUT, ":utf8";
my $streamer = AnyEvent::Twitter::Stream->new(
consumer_key => 'KEY',
consumer_secret => 'SECRET',
token => 'TOKEN',
token_secret => 'TOKEN SECRET',
method => "filter",
track => "KEYWORDS TO TRACK",
on_tweet => sub {
# CUSTOM CODE HERE
},
on_error => sub {
my $error = shift;
warn "ERROR: $error";
$done->send;
},
on_eof => sub {
$done->send;
},
);
$done->recv;
Try the OmniAuth gem which supports many external providers https://github.com/intridea/omniauth
You should use this gem : Tweetstream which sits on top of em-twitter

Resources