I have followed the guide below to obtain a Google Ads API refresh token for my application.
https://github.com/googleads/googleads-python-lib/wiki/API-access-on-behalf-of-your-clients-(web-flow)
Using the script below, everything worked, but the response only had an access token, while the refresh token was None.
from googleads import oauth2
import google.oauth2.credentials
import google_auth_oauthlib.flow
# Initialize the flow using the client ID and secret downloaded earlier.
# Note: You can use the GetAPIScope helper function to retrieve the
# appropriate scope for AdWords or Ad Manager.
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
'client_secret.json',
[oauth2.GetAPIScope('adwords')])
# Indicate where the API server will redirect the user after the user completes
# the authorization flow. The redirect URI is required.
flow.redirect_uri = 'https://www.example.com'
# Generate URL for request to Google's OAuth 2.0 server.
# Use kwargs to set optional request parameters.
authorization_url, state = flow.authorization_url(
# Enable offline access so that you can refresh an access token without
# re-prompting the user for permission. Recommended for web server apps.
access_type='offline',
# Enable incremental authorization. Recommended as a best practice.
include_granted_scopes='true',
# approval_prompt='force'
)
print("\n" + authorization_url)
print("\nVisit the above URL and grant access. You will be redirected. Get the 'code' from the query params of the redirect URL.")
auth_code = input('\nCode: ').strip()
flow.fetch_token(code=auth_code)
credentials = flow.credentials
print(credentials.__dict__)
The problem seemed to be that I have already completed these steps before.
The solution was to include approval_prompt='force' in flow.authorization_url(). After generating the authorization_url this way, the response included a refresh token as well.
Related
I am creating a Python script to use Microsoft Graph API services using the requests_oauthlib library. I am able to create successfully an OAuth2.0 session, get an authorization URL to open in an internet browser window to authenticate, and then I am redirected to the redirect URL that I previously indicated when I registered my app in the Azure portal (https://portal.azure.com). Then I copy the full redirect URL to paste into my application. At that point, my app reads the URL that I pasted, exchanges the authentication code that is embedded in the URL for an OAuth authentication token that is perfectly valid. To make sure, I check it in https://jwt.ms, and it is perfect except for the scopes granted. These scopes do not match the scopes that I requested in my OAuth session.
SCRIPT CODE
# details from the library can be found at https://pypi.org/project/requests-oauthlib/
from requests_oauthlib import OAuth2Session
client_id = <the client id from the Azure Portal when I registered my app>
client_secret = <the client secret I got from the Azure Portal>
redirect_uri = <the redirect_uri that I specified in the Azure Portal>
authorization_base_url = 'https://login.microsoftonline.com/<my tenant code>/oauth2/v2.0/authorize'
token_url = 'https://login.microsoftonline.com/<my tenant code>/oauth2/v2.0/token'
scopes = ["https://graph.microsoft.com/User.Read", "https://graph.microsoft.com/offline_access"]
# OAuth2.0 Authentication
msgraph = OAuth2Session(client_id, scope = scopes, redirect_uri=redirect_uri) # creates a OAuth 2.0 session object
# Redirect user to microsoft for authorization
# offline for refresh token
# force to always make user click authorize
authorization_url, state = msgraph.authorization_url(authorization_base_url, access_type="offline", prompt="select_account")
print('Please go here and authorize,', authorization_url) # user needs to click on this URL, authenticate and copy the URL that will be given
# Get the authorization verifier code from the callback url
redirect_response = input('Paste the full redirect URL here: ') # the user has to paste the url with the authorizaton code provided after authenticating
print('redirect_response: ', redirect_response)
# Fetches the access token AFTER the authentication code was given in the previous step
token = msgraph.fetch_token(token_url, client_secret=client_secret, authorization_response=redirect_response) # gets the access token
print('token: ', token)
but I get the following warning message:
Warning: Scope has changed from "https://graph.microsoft.com/User.Read https://graph.microsoft.com/offline_access" to "profile https://graph.microsoft.com/User.Read openid email".
API PERMISSIONS IN AZURE PORTAL
Microsoft Graph (2)
Files.ReadWrite.All
offline_access
As you can see in the Azure permissions above, the privileges (scopes) in the Azure portal are exactly the same scopes that I requested, so my question is where did these 'openid' and 'email' scopes come from? I have been able to overcome the warning message, but I can't request the privileges that I need. I even created a brand new application in the Azure portal, but I have the same problem. Is there something wrong with the requests_oauthlib library or I'm doing something wrong?
Thank you
When requesting scopes, you don't need a fully qualified domain name (FQDN) for Graph scopes (they're the default) and you shouldn't use them for non-Graph scopes (openid, profile, email, and offline_access are OpenID/AAD scopes, not Graph).
scopes = ["User.Read", "offline_access"]
For test automation purpose, I would like to use requests and requests-oauthlib library to access an API.
The API use an oauth2 authentication with google account, however it's not a google API. As my test tool should be able to run unattended, I would like to be able to obtain an access token that I could then refresh automatically for an indefinite amount of time.
Something looking like this example from requests-auth documentation would be great. It involves a manual login once, and then I can refresh the token.
https://requests-oauthlib.readthedocs.io/en/latest/examples/google.html
from requests_oauthlib import OAuth2Session
client_id="xxxx.apps.googleusercontent.com"
client_secret="xxxxx"
redirect_uri = 'https://localhost/callback'
authorization_base_url = "https://accounts.google.com/o/oauth2/auth"
token_url ="https://accounts.google.com/o/oauth2/token"
scope = [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
]
google = OAuth2Session(client_id, scope=scope, redirect_uri=redirect_uri)
# Redirect user to Google for authorization
authorization_url, state = google.authorization_url(authorization_base_url,
access_type="offline", prompt="select_account")
print 'Please go here and authorize,', authorization_url
redirect_response = raw_input('Paste the full redirect URL here:')
# Fetch the access token
google.fetch_token(token_url, client_secret=client_secret,
authorization_response=redirect_response)
r = google.get('https://www.googleapis.com/oauth2/v1/userinfo')
print r.content
However, I need to adapt to my API, and I can't find a way.
I can set authorization_base_url = "https://example.net/.auth/login/google"
and obtain the redirection URL but it doesn't work afterward.
I don't know either what I should set as scope.
Should I get Id and secret from the API provider ?
Or is there other solution ?
So i got a refresh token in this way and can I keep it?
And if so, how do I use it next time, so that there is no need for me to open browser?
Right now I'm thinking about creating OAuth2Credentials object directly, is this the right way?
from urllib.parse import urlparse, parse_qs
from oauth2client.client import flow_from_clientsecrets, OAuth2Credentials
from oauth2client.file import Storage
from oauth2client.tools import argparser, run_flow
from apiclient.discovery import build
from apiclient.errors import HttpError
from oauth2client.contrib import gce
import httplib2
import webbrowser
CLIENT_SECRETS_FILE = "bot_credentials.json"
flow = client.flow_from_clientsecrets(
CLIENT_SECRETS_FILE,
scope=scope,
redirect_uri='http://127.0.0.1:65010')
flow.params['include_granted_scopes'] = 'true'
flow.params['access_type'] = 'offline'
auth_uri = flow.step1_get_authorize_url()
webbrowser.open(auth_uri)
url = input('Please enter the redirected url with code')
code = get_url_param(url, 'code')
if code is None:
print('there is an error in your redirect link with code parameter, check if it exists')
exit()
print(code)
credentials = flow.step2_exchange(code[0])
print(credentials.to_json())#refresh_token here!!!
If the user consents to authorize your application to access those resources, Google will return a token to your application. Depending on your application's type, it will either validate the token or exchange it for a different type of token. Check this documentation.
For example, a server-side web application would exchange the returned token for an access token and a refresh token. The access token would let the application authorize requests on the user's behalf, and the refresh token would let the application retrieve a new access token when the original access token expires.
Basically, if your application obtains a refresh token during the authorization process, then you will need to periodically use that token to obtain a new, valid access token. Server-side web applications, installed applications, and devices all obtain refresh tokens.
It is stated here that at any time, your application can send a POST request to Google's authorization server that specifies your client ID, your client secret, and the refresh token for the user. The request should also set the grant_type parameter value to refresh_token.
The following example demonstrates this request:
POST /o/oauth2/token HTTP/1.1
Host: accounts.google.com
Content-Type: application/x-www-form-urlencoded
client_id=21302922996.apps.googleusercontent.com&
client_secret=XTHhXh1SlUNgvyWGwDk1EjXB&
refresh_token=1/6BMfW9j53gdGImsixUH6kU5RsR4zwI9lUVX-tqf8JXQ&
grant_type=refresh_token
The authorization server will return a JSON object that contains a new access token:
{
"access_token":"1/fFAGRNJru1FTz70BzhT3Zg",
"expires_in":3920,
"token_type":"Bearer"
}
You can check this sample on GitHub to generate a refresh token for the YouTube API. Note that this will will also create a file called generate_token.py-oauth that contains this information.
I am trying to get started with the Box.com SDK and I have a few questions.
from boxsdk import OAuth2
oauth = OAuth2(
client_id='YOUR_CLIENT_ID',
client_secret='YOUR_CLIENT_SECRET',
store_tokens=your_store_tokens_callback_method,
)
auth_url, csrf_token = oauth.get_authorization_url('http://YOUR_REDIRECT_URL')
def store_tokens(access_token, refresh_token):
# store the tokens at secure storage (e.g. Keychain)
1)What is the redirect URL and how do I use it? Do I need to have a server running to use this?
2)What sort of code to I need in the store_tokens method?
The redirect URL is only required if you're runng a Web application that needs to respond to user's requests to authenticate. If you're programtically authenticating, you can simply set this as http://localhost. In a scenario where you require the user to manually authenticate, the redirect URL should invoke some function in your web app to store and process the authentication code returned. Do you need a server running? Well, if you want to do something with the authentication code returned, the URL you specify should be under your control and invoke code to do something useful.
Here's an example of what the store_tokens function should look like. It should accept two parameters, access_token and refresh_token. In the example below, the function will commit these to a local store for use when the API needs to re-authenticate:
From here:
"""An example of Box authentication with external store"""
import keyring
from boxsdk import OAuth2
from boxsdk import Client
CLIENT_ID = 'specify your Box client_id here'
CLIENT_SECRET = 'specify your Box client_secret here'
def read_tokens():
"""Reads authorisation tokens from keyring"""
# Use keyring to read the tokens
auth_token = keyring.get_password('Box_Auth', 'mybox#box.com')
refresh_token = keyring.get_password('Box_Refresh', 'mybox#box.com')
return auth_token, refresh_token
def store_tokens(access_token, refresh_token):
"""Callback function when Box SDK refreshes tokens"""
# Use keyring to store the tokens
keyring.set_password('Box_Auth', 'mybox#box.com', access_token)
keyring.set_password('Box_Refresh', 'mybox#box.com', refresh_token)
def main():
"""Authentication against Box Example"""
# Retrieve tokens from secure store
access_token, refresh_token = read_tokens()
# Set up authorisation using the tokens we've retrieved
oauth = OAuth2(
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
access_token=access_token,
refresh_token=refresh_token,
store_tokens=store_tokens,
)
# Create the SDK client
client = Client(oauth)
# Get current user details and display
current_user = client.user(user_id='me').get()
print('Box User:', current_user.name)
if __name__ == '__main__':
main()
I suggest taking a look at the OAuth 2 tutorial. It will help give a better understanding of how OAuth works and what the various parameters are used for.
The redirect URL is set in your Box application's settings:
This is the URL where Box will send an auth code that can be used to obtain an access token. For example, if your redirect URL is set to https://myhost.com, then your server will receive a request with a URL that looks something like https://myhost.com?code=123456abcdef.
Note that your redirect URI doesn't need to be a real server. For example, apps that use a WebView will sometimes enter a fake redirect URL and then extract the auth code directly from the URL in the WebView.
The store_tokens callback is optional, but it can be used to save the access and refresh tokens in case your application needs to shutdown. It will be invoked every time the access token and refresh token changes, giving you an opportunity to save them somewhere (to disk, a DB, etc.).
You can then pass in these tokens to your OAuth2 constructor at a later time so that your users don't need to login again.
If you're just testing, you can also pass in a developer token. This tutorial explains how.
This is the most basic example that worked for me:
from boxsdk import Client, OAuth2
CLIENT_ID = ''
CLIENT_SECRET = ''
ACCESS_TOKEN = '' # this is the developer token
oauth2 = OAuth2(CLIENT_ID, CLIENT_SECRET, access_token=ACCESS_TOKEN)
client = Client(oauth2)
my = client.user(user_id='me').get()
print(my.name)
print(my.login)
print(my.avatar_url)
The Google Drive API has the following OAuth2.0 procedure from their quickstart to receive the drive_service at the end:
# Copy your credentials from the APIs Console
CLIENT_ID = 'YOUR_CLIENT_ID'
CLIENT_SECRET = 'YOUR_CLIENT_SECRET'
# Check https://developers.google.com/drive/scopes for all available scopes
OAUTH_SCOPE = 'https://www.googleapis.com/auth/drive'
# Redirect URI for installed apps
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'
# Path to the file to upload
FILENAME = 'document.txt'
# Run through the OAuth flow and retrieve credentials
flow = OAuth2WebServerFlow(CLIENT_ID, CLIENT_SECRET, OAUTH_SCOPE, REDIRECT_URI)
authorize_url = flow.step1_get_authorize_url()
print 'Go to the following link in your browser: ' + authorize_url
code = raw_input('Enter verification code: ').strip()
credentials = flow.step2_exchange(code)
# Create an httplib2.Http object and authorize it with our credentials
http = httplib2.Http()
http = credentials.authorize(http)
drive_service = build('drive', 'v2', http=http)
Notice that you will be given the variable authorize_url which is printed out. You are supposed to visit it using a browser and then confirm that you allow Google Drive to access your information, which then allows you get a "verification code." Is there any way that I can avoid the step of manual intervention and create a program that automates this step?
Yes, you can use web server to get OAuth callback which doesn't require any user interaction.
Basically, you set up your server to retrieve oauth code and add redirect uri to oauth flow so that oauth sends code to given uri instead of telling user to put code into the textbox.
Take a look at tools.run_flow() method at google-api-python-client.
It has pretty handy code of local webserver oauth flow.