Google API flow requires consent for refresh-token every time - python

Using python 3.6, requests==2.22.0
Trying to use the Google API, in particular mobile and desktop apps flow
I am able to generate an auth code by using this url:
url = (
'https://accounts.google.com/o/oauth2/v2/auth?'
'scope={scope}'
'response_type=code&'
'redirect_uri={redirect_uri}&'
'client_id={client_id}&'
'access_type=offline'.format(
redirect_uri=redirect_uri,
client_id=client_id,
scope=scope,
)
)
The redirect_uri I am using (for now) is simply https://google.com, and it is registered in the developer app I generated, in the Authorized redirect URIs section and in the Authorized domains section under the OAuth consent settings page/tab.
Once I paste the produced url in the browser - I get a code that I can extract and use to make the next call:
data = {
'client_id': client_id,
'client_secret': client_secret,
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': redirect_uri,
}
url = 'https://oauth2.googleapis.com/token'
response = requests.post(
url,
data=data,
headers={
'Content-Type': 'application/x-www-form-urlencoded',
},
print(response)
print(response.json())
The output:
<Response [200]>
{tokens dictionary} <-- more downstream
Here is the question:
In the beginning I was experimenting with the basic scopes from the various examples available everywhere: email+profile, and the result I got was this:
{'access_token': '******', 'expires_in': 3594, 'scope': 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile openid', 'token_type': 'Bearer', 'id_token': '******'} <-- id_token is a JWT
Next, I added the actual scopes I am interested in (and made sure to add them within the developer app):
https://www.googleapis.com/auth/calendar.events+https://www.googleapis.com/auth/calendar.readonly
The result I am getting is this:
{'access_token': '******', 'expires_in': 3595, 'scope': 'https://www.googleapis.com/auth/calendar.events https://www.googleapis.com/auth/calendar.readonly', 'token_type': 'Bearer'}
No refresh token? (I specifically require it to refresh the access token)
I then read this stack overflow post and added "&prompt=consent" to the code grant URL above: (https://accounts.google.com/o/oauth2/v2/auth)
Now I am getting:
{'access_token': '******', 'expires_in': 3600, 'refresh_token': '******', 'scope': 'https://www.googleapis.com/auth/calendar.events https://www.googleapis.com/auth/calendar.readonly', 'token_type': 'Bearer'}
I have the refresh token now, but is that the only way?
If the user will end up going through the flow again it will force another consent page flow - which should not be required after an initial consent was already given.
Is there any way to get the refresh token without an explicit consent every time?

Is there any way to get the refresh token without an explicit consent every time?
No. The refresh token is returned the first time with the user consent to off line access.
Google assumes that you have saved it and there for dont need another one. Revoke the users access and request access again you should get a refresh token. or sending request prompt will request that the user grant you off line access again and you will again get a new refresh token.

Related

Python (requests library) ETL: Spotify API "Authorization Code Flow" - Request Access Token Problem

Context:
I'm working on a side project to pull data from the Spotify API into a
Microsoft SQL Server database as part of a refreshing ETL job. I need
to use the "Authorization Code Flow" so I can authorize/authenticate
programmatically, so my table will populate each day.
I'm using the Python requests library for this, and I don't want to
make an Object Oriented Solution for this if possible (not my
preference).
Problem:
I'm having trouble getting the Access Token after authenticating.
Looking at similar issues, it's very similar to this one:
Spotify API Authorization Code Flow with Python
.
I'm not sure why I'm getting a Response 400 (Bad Request) from this.
Can someone please advise here?
Code:
# used to to encode byte string from CLIENT_ID : CLIENT_SECRET, then decode for Authentication Header
import base64
# used to make HTTP requests from Spotify API
import requests
# used to access the environment variables
import os
def request_user_authorization():
'''
HTTP GET request to gain access to data (Authorization Code Flow)
HTTP POST request to send the code and receive an Authorization Token (current issue)
https://developer.spotify.com/documentation/general/guides/authorization/code-flow/
'''
# URLs
AUTH_URL = 'https://accounts.spotify.com/authorize'
TOKEN_URL = 'https://accounts.spotify.com/api/token'
BASE_URL = 'https://api.spotify.com/v1'
SPOTIFY_URI = 'https://api.spotify.com/v1/me/player/recently-played'
# sensitive items
CLIENT_ID = os.environ.get('SPOTIFY_CLIENT_ID_ENV')
CLIENT_SECRET = os.environ.get('SPOTIFY_CLIENT_SECRET_ENV')
# make a request to the /authorize endpoint to get an authorization code
user_authorization_code = requests.get(
AUTH_URL, {
'client_id': CLIENT_ID,
'response_type': 'code',
'redirect_uri': SPOTIFY_URI,
'scope': 'user-read-recently-played',
}
)
# Code 200 = "OK"
print(user_authorization_code)
#----------------------------------------------------------#
api_header_string = base64.urlsafe_b64encode((CLIENT_ID + ':' + CLIENT_SECRET).encode('ascii'))
api_headers={
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic %s' % api_header_string.decode('ascii')
}
api_payload = {
'grant_type': 'authorization_code',
'code': user_authorization_code,
'redirect_uri': SPOTIFY_URI,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET
}
#issue here:
# Make a request to the /token endpoint to get an access token
access_token_request = requests.post(url=TOKEN_URL, data=api_payload, headers=api_headers)
# returns <Response [400]>
# https://datatracker.ietf.org/doc/draft-ietf-httpbis-semantics/
# 15.5.1. 400 Bad Request
# The _400 (Bad Request)_ status code indicates that the server cannot
# or will not process the request due to something that is perceived to
# be a client error (e.g., malformed request syntax, invalid request
# message framing, or deceptive request routing).
# print(access_token_request)
#----------------------------------------------------------#
request_user_authorization()
You seem to have misunderstood how the Authorizatuon Code Flow works.
The redirect_uri in this kind of flow is used by the provider api (here spotify) as a callback to give you the authorization code.
The spotify API will call this url with a code parameter that you can use to ask for a token.
Meaning that for this flow to work you need a web server ready to receive requests on the uri that you have given in your code request (and specified when creating your app on the spotify developer portal). You might be better off using the Client Credentials Flow for your use case.
Also you should always use the name of the keywords arguments when using requests.get, requests.post ... It makes the code clearer and the order of the arguments differ for each method so it can get confusing if you don't.
#Speedlulu you're correct, that was the problem.
For anyone in the future reading this question: this is what I learned since posting the question:
What I misunderstood was the flow of data, and that Client Credentials Flow (Application to Spotify only) was the better choice because I don't need to have a "User" portion to this program.
Spotify's Client Credentials Flow Documentation: https://developer.spotify.com/documentation/general/guides/authorization/client-credentials/
# used to access environment variables securely (sensitive data)
import os
# used to encode strings into bytes and back
import base64
# used to convert JSON data into strings
import json
# endpoint that I'm connecting to on Spotify's servers
token_request_url = "https://accounts.spotify.com/api/token"
CLIENT_ID = os.environ.get('SPOTIFY_CLIENT_ID_ENV')
CLIENT_SECRET = os.environ.get('SPOTIFY_CLIENT_SECRET_ENV')
# encode credentials into bytes, then decode into a string for the HTTP POST request to Spotify to authenticate
BASE64_ENCODED_HEADER_STRING = base64.b64encode(bytes(f"{CLIENT_ID}:{CLIENT_SECRET}", "ISO-8859-1")).decode("ascii")
#initializing dictionaries for HTTP POST request
headers = {}
data = {}
headers['Authorization'] = f"Basic {BASE64_ENCODED_HEADER_STRING}"
data['grant_type'] = "client_credentials"
data['json'] = True
data['scope'] = 'user-read-recently-played'
r = requests.post(url=token_request_url, headers=headers, data=data)
# prints the response from the server regarding the access token data (formatted to be easier to read)
print(json.dumps(r.json(), indent=2))
# store the token value in a variable for HTTP GET request
token = r.json()['access_token']
What was unclear is that I first need to POST my request with the credentials to get the token (using the specific URL to do), store the r.json()['access_token'] value in a variable, then use that as part of the following GET request to access my specific data.

Can't fetch slack user profile information with API

Thank you so much in advance. I am trying to fetch user profile information through slack_authentication. Although the app is successfully authenticated with Slack, it could not get email and username.
{'ok': True, 'access_token': 'xoxp-xXXXXXXXXXXXXXXXX', 'scope': 'identify,channels:read,users.profile:read,chat:write:bot,identity.basic', 'user_id': 'XXXXXXXXX', 'team_id': 'XXXXXXXX', 'enterprise_id': None, 'team_name': 'test', 'warning': 'superfluous_charset', 'response_metadata': {'warnings': ['superfluous_charset']}}
I tried to add identify scope instead of identity.basic because slack doesn't allow you to use both identity.basic and other scopes.
The code is below:
#bp.route('/redirect', methods=['GET'])
def authorize():
authorize_url = f"https://slack.com/oauth/authorize?scope={ oauth_scope }&client_id={ client_id }"
return authorize_url
#bp.route('/callback', methods=["GET", "POST"])
def callback():
auth_code = request.args['code']
client = slack.WebClient(token="")
response = client.oauth_access(
client_id=client_id,
client_secret=client_secret,
code=auth_code
)
print(response)
Additional
I have realized how to get users info. I updated the code to like this.
The code is updated like below:
oauth = client.oauth_access(
client_id=client_id,
client_secret=client_secret,
code=auth_code
)
user_id = oauth['user_id']
response = client.users_info(user=user_id)
But this error occurs:
The server responded with: {'ok': False, 'error': 'not_authed'}
Your code looks like an installation routine for a Slack app using OAuth. But it does not contain a call to get a user profile.
To get the profile of a user you can call users.info and provide the ID of the user you are interested in.
Examples:
response = client.users_info(user=ID_OF_USER)
assert(response)
profile = response['user']['profile']
email = response['user']['profile']['email']
In order to retrieve the user's profile and email address you need these scopes:
- users:read
- users:read.email
The identity scopes are unrelated to the user profile. They are used for the "Sign-in with Slack" approach only, where you can authenticate with a Slack user on a 3rd party web site.
Finally, just to clarify, because this is often misunderstood: You only need to run through the OAuth installation routine once. The routine will yield you a token for the workspace, which you can store and use for any further calls to the API for that workspace.
Update to "Additional"
You are not using the API correctly.
You need to first complete the Oauth flow and collect the access token, which is in the response from client.oauth_access.
Then you need to initialize a new WebClient with the token you received. With the new client you can then access all API methods, like users.info etc.
Again: You should run through the OAuth process one time only and store the received token for later use.
Example:
oauth_info = client.oauth_access(
client_id=client_id,
client_secret=client_secret,
code=auth_code
)
access_token = oauth_info['access_token'] # you want to store this token in a database
client = slack.WebClient(token=access_token)
user_id = oauth_info['user_id']
response = client.users_info(user=user_id)
print(response)

Retrieving access token without manual copying

I make the following request:
https://oauth.vk.com/authorize?redirect_uri=https%3A%2F%2Foauth.vk.com%2Fblank.html&response_type=token&client_id=5842359&v=5.63&scope=friends%2Coffline&display=page
to retrieve an access token. This url leads to a login page on vk.com (if not logged in already), then prompts a user to authorize application and then redirects to https://oauth.vk.com/blank.html#access_token={token}&expires_in=0&user_id={id}. So to actually retrieve an access token one needs to manually copy it from the address bar. This procedure is specified in the official API. Is there a way to go around this? How do I get a token using only python code?
Below is the procedure that generates the url:
import requests
def authorize_app(client_id, redirect_uri = None):
'''
The function generates an url, which redirects to login page (optional, if not logged in) and app authorization.
'''
if redirect_uri == None:
redirect_uri = 'https://oauth.vk.com/blank.html' # default callback url
oauth = requests.get('https://oauth.vk.com/authorize', params = {'client_id' : str(client_id),
'redirect_uri' : redirect_uri,
'display' : 'page',
'scope' : ['friends,offline'], # offline option makes the token permanent
'response_type' : 'token',
'v' : 5.63})
return oauth.url
You can use Authorization Code flow wherein you receive Auth_code and then use this code to retrieve access_token.
Getting auth_code and access_tokens are just POST requests to the OAuth server.
In your code response_type should be code to get the auth code and then use this code to retrieve acccess_token
Refer to this https://vk.com/dev/auth_sites. Generally, this flow is same on any OAuth provider.

Spotify "Unexpected status: 400" when refreshing and accessing token - python

When trying to authorize spotify using python 3, I get a "server_error" with the description "Unexpected status: 400".
I am using the correct authorization code and the spotify documentation (https://developer.spotify.com/web-api/authorization-guide/) instructed me to use a post command with those parameters.
I'm quite a noob in this and I do not know what I am doing wrong.
Here is the code:
import requests
params = {'grant_type': 'authorization_code', 'code': authcode, 'redirect_uri': 'https://example.com/callback','client_id':'example', 'client_secret':'example'}
req=requests.post('https://accounts.spotify.com/api/token', params=params)
print(req.content)
According to spotify's own guide (see step #4):
https://developer.spotify.com/web-api/authorization-guide/
The authorization info for requesting a new token must go in the header via an "Authorization" variable:
Authorization: Required. Base 64 encoded string that contains the
client ID and client secret key. The field must have the format:
Authorization: Basic base64 encoded client_id:client_secret
You have it instead in the request body itself.
So you should do something like:
import requests
import base64
authcode = 'valid_authcode_from_prev_authorization_step'
params = {'grant_type': 'authorization_code', 'code': authcode, 'redirect_uri': 'https://example.com/callback'}
client_id = 'example_id'
client_secret = 'example_secret'
b64_val = base64.b64encode("%s:%s" % (client_id, client_secret))
req = requests.post(
'https://accounts.spotify.com/api/token', params=params,
headers={'Authorization': b64_val})
However, for this to work you need a valid auth code which you can only get by having the user go through the auth step which precedes the token acquisition step.
This code gets sent to the callback you have registered in your app settings, which won't work if you have a fake callback set (ie: http://example.com/callback).

how to get authorization code for Box in python

I was stuck in 'Getting the Access Token' part. In the documentation, I need a 'code'. But how can I get the authorization code? I'm using python.
authorize_url = 'https://www.box.com/api/oauth2/authorize?response_type=code&client_id=MY_CLIENT_ID'
def myRequest(path, method=None, options=None):
response = requests.request(method, path, **options)
return json.dumps(response.json())
code = myRequest(
path=authorize_url,
method='GET',
options={
'headers':{
'response_type': 'code',
'client_id': CLIENT_ID,
}
}
)
You have to use a module to make HTTP requests e.g. requests or urllib and follow the instructions as indicated here.
I have a very simple Flask app set up here: https://github.com/seanrose/box-oauth2-example
The 'code' is returned to your app after you send the user to Box's authorization URL.

Categories