Retrieve access token in Azure with Ruby SDK/APIs - ruby-on-rails

I am trying to retrieve access token using azure app client id and client secret . Initially I tried with the following python code block
import adal
context = adal.AuthenticationContext(AUTHORITY)
token = context.acquire_token_with_client_credentials(
"https://management.azure.com/",
CLIENT_ID,
CLIENT_SECRET)
This is returning the token without any issue .
I am trying to do the same using Azure Ruby SDK following the contents in https://github.com/Azure/azure-sdk-for-ruby but still not able to get any sample to follow .
I am a beginner in ruby ,can some body please share their experience with me on this ?
Added to my post from here on
Hi ,
Many thanks for your support .
I followed you code and written my code like the below one following your code
require 'adal'
TENANT=<TENANT ID>
CLIENT_ID= <CLIENT_ID>
CLIENT_SECRET =<CLIENT_SECRET >
AUTHORITY = "https://login.windows.net"
auth_ctx = ADAL::AuthenticationContext.new(AUTHORITY, TENANT)
client_cred = ADAL::ClientCredential.new(CLIENT_ID, CLIENT_SECRET)
result = auth_ctx.acquire_token_for_client("https://management.azure.com/", client_cred)
puts result.access_token
But I am getting an error like the following ,
check_host': bad component(expected host component)
In Python it worked for me though .
Following is the full error trace .
F:/All_Ruby_On_Rails/ruby-2.2.6-x64-mingw32/lib/ruby/2.2.0/uri/generic.rb:593:in `check_host': bad component(expected host component): [https://login.windows.net] (URI::InvalidComponentError)
from F:/All_Ruby_On_Rails/ruby-2.2.6-x64-mingw32/lib/ruby/2.2.0/uri/generic.rb:634:in `host='
from F:/All_Ruby_On_Rails/ruby-2.2.6-x64-mingw32/lib/ruby/2.2.0/uri/generic.rb:668:in `hostname='
from F:/All_Ruby_On_Rails/ruby-2.2.6-x64-mingw32/lib/ruby/2.2.0/uri/generic.rb:187:in `initialize'
from F:/All_Ruby_On_Rails/ruby-2.2.6-x64-mingw32/lib/ruby/2.2.0/uri/generic.rb:134:in `new'
from F:/All_Ruby_On_Rails/ruby-2.2.6-x64-mingw32/lib/ruby/2.2.0/uri/generic.rb:134:in `build'
from F:/All_Ruby_On_Rails/ruby-2.2.6-x64-mingw32/lib/ruby/2.2.0/uri/http.rb:62:in `build'
from F:/All_Ruby_On_Rails/ruby-2.2.6-x64-mingw32/lib/ruby/gems/2.2.0/gems/adal-1.0.0/lib/adal/authority.rb:95:in `token_endpoint'
from F:/All_Ruby_On_Rails/ruby-2.2.6-x64-mingw32/lib/ruby/gems/2.2.0/gems/adal-1.0.0/lib/adal/token_request.rb:228:in `oauth_request'
from F:/All_Ruby_On_Rails/ruby-2.2.6-x64-mingw32/lib/ruby/gems/2.2.0/gems/adal-1.0.0/lib/adal/token_request.rb:182:in `request_no_cache'
from F:/All_Ruby_On_Rails/ruby-2.2.6-x64-mingw32/lib/ruby/gems/2.2.0/gems/adal-1.0.0/lib/adal/token_request.rb:171:in `request'
from F:/All_Ruby_On_Rails/ruby-2.2.6-x64-mingw32/lib/ruby/gems/2.2.0/gems/adal-1.0.0/lib/adal/token_request.rb:84:in `get_for_client'
from F:/All_Ruby_On_Rails/ruby-2.2.6-x64-mingw32/lib/ruby/gems/2.2.0/gems/adal-1.0.0/lib/adal/authentication_context.rb:78:in `acquire_token_for_client'
from F:/Selenium_Workspace_HSBC/dsi/azureadallogin.rb:9:in `<main>'
It looks to me the AUTHORITY constant has the issue .Can anybody provide some clue here ?

Welp, he's the copy\paste:
# Create authentication objects
token_provider = MsRestAzure::ApplicationTokenProvider.new(tenant_id, client_id, secret)
credentials = MsRest::TokenCredentials.new(token_provider)
# Create a client - a point of access to the API and set the subscription id
client = Azure::ARM::Resources::ResourceManagementClient.new(credentials)
client.subscription_id = subscription_id
https://github.com/Azure/azure-sdk-for-ruby/tree/master/management/azure_mgmt_resources

Otherwise, you can use the ADAL for Ruby library to get the access token like using Python ADAL as the code you post.
First of all, install adal via gem install adal.
Then,
Follow the adal sample with CLIENT_ID & CLIENT_SECRET to get the access token via the code below using the method acquire_token_for_client.
require 'adal'
AUTHORITY = 'login.windows.net'
auth_ctx = ADAL::AuthenticationContext.new(AUTHORITY, TENANT)
client_cred = ADAL::ClientCredential.new(CLIENT_ID, CLIENT_SECRET)
result = auth_ctx.acquire_token_for_client("https://management.azure.com/", client_cred)
puts result.access_token
Follow the adal sample with USERNAME & PASSWORD to get the access token via the code below.
require 'adal'
AUTHORITY = 'login.windows.net'
user_cred = ADAL::UserCredential.new(username, password)
ctx = ADAL::AuthenticationContext.new(AUTHORITY_HOST, TENANT)
result = ctx.acquire_token_for_user("https://management.azure.com/", CLIENT_ID, user_cred)
puts result.access_token
Hope it helps.

Related

Google OAuth 2.0 failing with Error 400: invalid_request for some client_id, but works well for others in the same project

We have some apps (or maybe we should call them a handful of scripts) that use Google APIs to facilitate some administrative tasks. Recently, after making another client_id in the same project, I started getting an error message similar to the one described in localhost redirect_uri does not work for Google Oauth2 (results in 400: invalid_request error). I.e.,
Error 400: invalid_request
You can't sign in to this app because it doesn't comply with Google's
OAuth 2.0 policy for keeping apps secure.
You can let the app developer know that this app doesn't comply with
one or more Google validation rules.
Request details:
The content in this section has been provided by the app developer.
This content has not been reviewed or verified by Google.
If you’re the app developer, make sure that these request details
comply with Google policies.
redirect_uri: urn:ietf:wg:oauth:2.0:oob
How do I get through this error? It is important to note that:
The OAuth consent screen for this project is marked as "Internal". Therefore any mentions of Google review of the project, or publishing status are irrelevant
I do have "Trust internal, domain-owned apps" enabled for the domain
Another client id in the same project works and there are no obvious differences between the client IDs - they are both "Desktop" type which only gives me a Client ID and Client secret that are different
This is a command line script, so I use the "copy/paste" verification method as documented here hence the urn:ietf:wg:oauth:2.0:oob redirect URI (copy/paste is the only friendly way to run this on a headless machine which has no browser).
I was able to reproduce the same problem in a dev domain. I have three client ids. The oldest one is from January 2021, another one from December 2021, and one I created today - March 2022. Of those, only the December 2021 works and lets me choose which account to authenticate with before it either accepts it or rejects it with "Error 403: org_internal" (this is expected). The other two give me an "Error 400: invalid_request" and do not even let me choose the "internal" account. Here are the URLs generated by my app (I use the ruby google client APIs) and the only difference between them is the client_id - January 2021, December 2021, March 2022.
Here is the part of the code around the authorization flow, and the URLs for the different client IDs are what was produced on the $stderr.puts url line. It is pretty much the same thing as documented in the official example here (version as of this writing).
OOB_URI = 'urn:ietf:wg:oauth:2.0:oob'
def user_credentials_for(scope, user_id = 'default')
token_store = Google::Auth::Stores::FileTokenStore.new(:file => token_store_path)
authorizer = Google::Auth::UserAuthorizer.new(client_id, scope, token_store)
credentials = authorizer.get_credentials(user_id)
if credentials.nil?
url = authorizer.get_authorization_url(base_url: OOB_URI)
$stderr.puts ""
$stderr.puts "-----------------------------------------------"
$stderr.puts "Requesting authorization for '#{user_id}'"
$stderr.puts "Open the following URL in your browser and authorize the application."
$stderr.puts url
code = $stdin.readline.chomp
$stderr.puts "-----------------------------------------------"
credentials = authorizer.get_and_store_credentials_from_code(
user_id: user_id, code: code, base_url: OOB_URI)
end
credentials
end
Please see https://stackoverflow.com/a/71491500/1213346 for a "proper" solution. This answer is just an ugly workaround that the community seems to like.
...
Here is a cringy workaround for this situation:
Replace urn:ietf:wg:oauth:2.0:oob with http://localhost:1/ in the code posted in the question. This makes the flow go through, my browser gets redirected and fails and I get an error messages like:
This site can’t be reached
The webpage at http://localhost:1/oauth2callback?
code=4/a3MU9MlhWxit8P7N8QsGtT0ye8GJygOeCa3MU9MlhWxit8P7N8QsGtT0y
e8GJygOeC&scope=email%20profile%20https... might be temporarily
down or it may have moved permanently to a new web address.
ERR_UNSAFE_PORT
Now copy the code code value from the failing URL, paste it into the app, and voila... same as before :)
P.S. Here is the updated "working" version:
def user_credentials_for(scope, user_id = 'default')
token_store = Google::Auth::Stores::FileTokenStore.new(:file => token_store_path)
authorizer = Google::Auth::UserAuthorizer.new(client_id, scope, token_store, "http://localhost:1/")
credentials = authorizer.get_credentials(user_id)
if credentials.nil?
url = authorizer.get_authorization_url
$stderr.puts ""
$stderr.puts "-----------------------------------------------"
$stderr.puts "Requesting authorization for '#{user_id}'"
$stderr.puts "Open the following URL in your browser and authorize the application."
$stderr.puts url
$stderr.puts
$stderr.puts "At the end the browser will fail to connect to http://localhost:1/?code=SOMECODE&scope=..."
$stderr.puts "Copy the value of SOMECODE from the address and paste it below"
code = $stdin.readline.chomp
$stderr.puts "-----------------------------------------------"
credentials = authorizer.get_and_store_credentials_from_code(
user_id: user_id, code: code)
end
credentials
end ```
I sent off an email to someone on the Google OAuth team. This is the gist of their response.
As I feared your issue is related to Making Google OAuth interactions safer by using more secure OAuth flows
The current recommendation from google is to move to use localhost/loopback redirects as recommended here: instructions-oob or use the OAuth for devices flow if you are using non-sensitive scopes and need a headless solution.
A solution for python.
As google_auth_oauthlib shows, InstalledAppFlow.run_console has been deprecated after Feb 28, 2022. And if you are using google-ads-python, you can just replace flow.run_console() by flow.run_local_server().
Let me post the "proper" solution as a separate answer, which is to actually follow the recommended procedure by implementing an HTTP listener in the ruby app. If this is running on an offline machine the listener will never get the code, but you can still paste the code from the failing URL.
require 'colorize'
require 'sinatra/base'
# A simplistic local server to receive authorization tokens from the browser
def run_local_server(authorizer, port, user_id)
require 'thin'
Thin::Logging.silent = true
Thread.new {
Thread.current[:server] = Sinatra.new do
enable :quiet
disable :logging
set :port, port
set :server, %w[ thin ]
get "/" do
request = Rack::Request.new env
state = {
code: request["code"],
error: request["error"],
scope: request["scope"]
}
raise Signet::AuthorizationError, ("Authorization error: %s" % [ state[:error] ] ) if state[:error]
raise Signet::AuthorizationError, "Authorization code missing from the request" if state[:code].nil?
credentials = authorizer.get_and_store_credentials_from_code(
user_id: user_id,
code: state[:code],
scope: state[:scope],
)
[
200,
{ "Content-Type" => "text/plain" },
"All seems to be OK. You can close this window and press ENTER in the application to proceed.",
]
end
end
Thread.current[:server].run!
}
end
# Returns user credentials for the given scope. Requests authorization
# if requrired.
def user_credentials_for(scope, user_id = 'default')
client_id = Google::Auth::ClientId.new(ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'])
token_store = Google::Auth::Stores::FileTokenStore.new(:file => ENV['GOOGLE_CREDENTIAL_STORE'])
port = 6969
redirect_uri = "http://localhost:#{port}/"
authorizer = Google::Auth::UserAuthorizer.new(client_id, scope, token_store, redirect_uri)
credentials = authorizer.get_credentials(user_id)
if credentials.nil? then
server_thread = run_local_server(authorizer, port, user_id)
url = authorizer.get_authorization_url
$stderr.puts ""
$stderr.puts "-----------------------------------------------"
$stderr.puts "Requesting authorization for '#{user_id.yellow}'"
$stderr.puts "Open the following URL in your browser and authorize the application."
$stderr.puts
$stderr.puts url.yellow.bold
$stderr.puts
$stderr.puts "⚠️ If you are authorizing on a different machine, you will have to port-forward"
$stderr.puts "so your browser can reach #{redirect_uri.yellow}"
$stderr.puts
$stderr.puts "⚠️ If you get a " << "This site can't be reached".red << " error in the browser,"
$stderr.puts "just copy the failing URL below. Copy the whole thing, starting with #{redirect_uri.yellow}."
$stderr.puts "-----------------------------------------------"
code = $stdin.readline.chomp
server_thread[:server].stop!
server_thread.join
credentials = authorizer.get_credentials(user_id)
# If the redirect failed, the user must have provided us with a code on their own
if credentials.nil? then
begin
require 'uri'
require 'cgi'
code = CGI.parse(URI.parse(code).query)['code'][0]
rescue StandardException
# Noop, if we could not get a code out of the URL, maybe it was
# not the URL but the actual code.
end
credentials = authorizer.get_and_store_credentials_from_code(
user_id: user_id,
code: code,
scope: scope,
)
end
end
credentials
end
credentials = user_credentials_for(['https://www.googleapis.com/auth/drive.readonly'])
In short, we run a web server expecting the redirect from the browser. It takes the code the browser sent, or it takes the code pasted by the user.
For headless Python scripts that need sensitive scopes, continuing to use run_console now produces the following (and the flow likely fails):
DeprecationWarning: New clients will be unable to use `InstalledAppFlow.run_console` starting on Feb 28, 2022. All clients will be unable to use this method starting on Oct 3, 2022. Use `InstalledAppFlow.run_local_server` instead. For details on the OOB flow deprecation, see https://developers.googleblog.com/2022/02/making-oauth-flows-safer.html?m=1#disallowed-oob
The official solution is to migrate to a flow that spins up a local server to handle the OAuth redirect, but this will not work on remote headless systems.
The solution Google adopted in gcloud is to run a local server on the same machine as the user's browser and then have the user copy the redirect URL requested from this local server back to the remote machine. Note that this requires having gcloud installed both on the remote machine and on the user's workstation.
As a hack for situations where installing a script to echo back the redirect URL on the workstation is not practical, we can use a redirect URL that is guaranteed to fail and just have the user copy back the URL of the error page on which they will land after authorization is complete.
import urllib
from google_auth_oauthlib.flow import InstalledAppFlow
def run_console_hack(flow):
flow.redirect_uri = 'http://localhost:1'
auth_url, _ = flow.authorization_url()
print(
"Visit the following URL:",
auth_url,
"After granting permissions, you will be redirected to an error page",
"Copy the URL of that error page (http://localhost:1/?state=...)",
sep="\n"
)
redir_url = input("URL: ")
query = urllib.parse.urlparse(redir_url).query
code = urllib.parse.parse_qs(query)['code'][0]
flow.fetch_token(code=code)
return flow.credentials
scopes = ['https://www.googleapis.com/auth/drive.file']
flow = InstalledAppFlow.from_client_secrets_file(secrets_file, scopes)
credentials = run_console_hack(flow)
We could also ask the user to pass back the code query string parameter directly but that is likely to be confusing and error-prone.
The use of 1 as the port number means that the request is guaranteed to fail, rather than potentially hit some service that happens to be running on that port. (e.g. Chrome will fail with ERR_UNSAFE_PORT without even trying to connect)
"Hello world" for this error:
Generating an authentication URL
https://github.com/googleapis/google-api-nodejs-client#generating-an-authentication-url
const {google} = require('googleapis');
const oauth2Client = new google.auth.OAuth2(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_REDIRECT_URL
);
// generate a url that asks permissions for Blogger and Google Calendar scopes
const scopes = [
'https://www.googleapis.com/auth/blogger',
'https://www.googleapis.com/auth/calendar'
];
const url = oauth2Client.generateAuthUrl({
// 'online' (default) or 'offline' (gets refresh_token)
access_type: 'offline',
// If you only need one scope you can pass it as a string
scope: scopes
});
If something goes wrong the first step is to Re Check again the three values of the google.auth.OAuth2 function.
1 of 2
Compare to the store values under Google APIs console:
YOUR_CLIENT_ID
YOUR_CLIENT_SECRET
YOUR_REDIRECT_URL -
For example http://localhost:3000/login
2 of 2 (environment variables)
A lot of times the values store inside .env. So re-check the env and the output under your files - for example index.ts (Even use console.log).
.env
# Google Sign-In (OAuth)
G_CLIENT_ID=some_id_1234
G_CLIENT_SECRET=some_secret_1234
PUBLIC_URL=http://localhost:3000
index
const auth = new google.auth.OAuth2(
process.env.G_CLIENT_ID,
process.env.G_CLIENT_SECRET,
`${process.env.PUBLIC_URL}/login`
);
SUM:
Something like this will not work
const oauth2Client = new google.auth.OAuth2(
"no_such_id",
"no_such_secret",
"http://localhost:3000/i_forgot_to_Authorised_this_url"
);
I've fixed this problem with recreate my App in google console. And I think the problem was with redirect_url. I had this problem when I was using 'Android' type of App in google console (in this case you can't configure redirect url). In my android App I'm using google auth with WebView so the best option here use use 'Web' type for your app in google console.
In my case, had to update plugins. by running following command-
bundle exec fastlane update_plugins
With this redirect uri was getting created properly as
https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force&client_id=563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com&include_granted_scopes=true&redirect_uri=http://localhost:8081&response_type=code&scope=https://www.googleapis.com/auth/cloud-platform&state=2ce8a59b2d403f3a89fa635402bfc5c4
steps.oauth.v2.invalid_request 400 This error name is used for multiple different kinds of errors, typically for missing or incorrect parameters sent in the request. If is set to false, use fault variables (described below) to retrieve details about the error, such as the fault name and cause.
GenerateAccessToken GenerateAuthorizationCode
GenerateAccessTokenImplicitGrant
RefreshAccessToken
Google Oauth Policy

Snowflake custom OAuth client fails with invalid_client error?

I have created a custom OAuth client for the snowflake account by referring documentation here, https://docs.snowflake.com/en/user-guide/oauth-custom.html
I created an OAuth custom instance for my local using the following query:
create security integration My_Snowflake_Connector
type = oauth
enabled = true
oauth_client = custom
oauth_client_type = 'CONFIDENTIAL'
oauth_redirect_uri = 'http://localhost:4200/api/auth/callback/snowflake'
oauth_issue_refresh_tokens = true
oauth_refresh_token_validity = 86400
blocked_roles_list = ()
pre_authorized_roles_list = ('SYSADMIN', 'ACCOUNTADMIN', 'SECURITYADMIN')
oauth_allow_non_tls_redirect_uri = true
I have obtained authorization & token URLs using the following query:
DESCRIBE security integration My_Snowflake_Connector
I also obtained secrets using the following query:
SELECT SYSTEM$SHOW_OAUTH_CLIENT_SECRETS( 'MY_SNOWFLAKE_CONNEECTOR' )
I have used passport-oauth2 plugin & OAuth2Strategy
On initiating OAuth flow I am rightly taken to the snowflake account I log in it shows the OAuth consent screen but upon redirection, I get an error invalid_client.
I am getting the following error JSON blob:
{\n "data" : null,\n "error" : "invalid_client",\n "code" : null,\n "message" : "This is an invalid client.",\n "success" : false,\n "headers" : null\n}
I have verified the callback URL, client id & secret none seems to be wrong.
What may be wrong with my configuration?
Update
We could not figure out the reason behind the OAuth error, Finally, we ended up using the Node js client from snowflake. For more info: https://docs.snowflake.com/en/user-guide/nodejs-driver.html
I could see a similar error when testing OAuth2 from Postman when Client Authentication is set to "Send Client Credentials in body".
On changing this to "Send as Basic Auth Header",token generation works fine and proceeds successfully.
I guess this change should resolve the issue in your case as well. One of the reference that I checked is here : https://github.com/ciaranj/node-oauth/pull/316

Cloud Function & OAuth 2.0

I have a issue to use OAuth2.0 on GCP Cloud Function. I use to run this code locally. it works and it open a web browser's page to ask access to my gmail account.
I know that InstalledAppFlow is only use for local application.
SCOPES = ['https://mail.google.com/']
creds = None
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES) # <-- Oauth2.0 credential
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
I then tried to do another way using /tmp repesitory to store the token but still doesn't work and I can't see where is the issue ... Do you have any idea ? thank you so much
SCOPES = ['https://mail.google.com/']
CLIENT_SECRET_FILE = 'credentials.json' #OAuth credentials
APPLICATION_NAME = 'Gmail API Python'
def get_credentials():
store = oauth2client.file.Storage("/tmp/tempcredentials.json")
credentials = store.get()
if not credentials or credentials.invalid:
flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
flow.user_agent = APPLICATION_NAME
credentials = tools.run_flow(flow, store)
return credentials
As stated in the documentation the temporary folder is just for creating temporary files that will be stored in RAM memory and only available for the instance currently executing your code, thus there's no guarantee of persistence between invocations.
You should check this tutorial as it explains how to authenticate from Cloud Functions into Gmail (you will need more than a single function).

Withings API Status Code 2555

I'm trying to integrate Withings with a rails apps. I'm using an Omniauth provider someone wrote called omniauth-withings. I was able to configure the provider to allow me to visit /auth/withings which redirects to the Withings authorization page. After I allow access, the browser is redirected to the callback url /auth/withings/callback. I have this routed to a controller action that attempts to get the measurement data from Withings using the simplificator-withings gem.
Withings.consumer_secret = ENV['withings_app_key']
Withings.consumer_key = ENV['withings_app_secret']
auth_hash = request.env['omniauth.auth']
user_id = auth_hash.extra.raw_info.body.users.first.id
withings_user = User.authenticate(user_id, auth_hash.credentials.token, auth_hash.credentials.secret)
measurements = withings_user.measurement_groups(:device => Withings::SCALE)
The problem happens when I call User.authenticate(), I get this:
An unknown error occurred - Status code: 2555
Is there something I'm missing here?
I was getting the same error with a django app. It turns out I was using the wrong token and secret. I was using the oauth_token and oauth_token_secret returned from step 1 of the authorization process, rather than the oauth_token and oauth_token_secret from step 3. Make sure you are using the values from step 3. The API documentation shows the same values returned from these calls, but they will be different. Hopefully this helps you too.

Use google-api-ruby-client gem have redirect_uri_mismatch error

I want use this API in rails.
It says should include an Authorization header.(use oauth2)
So I use google-api-ruby-client this lib like below.
I write below code by this sample.
#client = Google::APIClient.new
#client.authorization.client_id = CONSUMER_KEY
#client.authorization.client_secret = CONSUMER_SECRET
#client.authorization.scope = 'https://apps-apis.google.com/a/feeds/domain/'
#client.authorization.redirect_uri = "http://#{request.host}:#{request.port.to_s}
/google_app/oauth2callback"
redirect_to #client.authorization.authorization_uri.to_s
But it cause redirect_uri_mismatch error.
I don't know whether my usage is correct.
Note:
Before use this API, I have logined with Google Openid successfully.
might be a duplicate of OAuth 2.0 sample error when accessing Google API
the problem there is, that the javascript is missing the port-number.

Resources