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).
Related
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.
I have set up API access to a website and can use their test GUI interface to connect (HTTP 200) using the username, passsword and API key.
If I try code below I get error Response 403:
from requests.auth import HTTPDigestAuth
url = 'https://website.com'
result = requests.get(url, auth=HTTPDigestAuth('username', 'password'))
I think I need to include the API key with the original requests.get but I'm not sure how to do that.
It's a REST API. On the website I generated an API key by providing a Name and then set the API login details (username and password). Under info on the HTTP requests it lists Request Header components:
X-API-KEY: k The API key k (obtained from My Account) is how we identify and authorise the calling application
CST / Authorization A valid access token identifying the client.
X-SECURITY-TOKEN / ACCOUNT-ID A valid account token or account id identifying the client's current account.
Content-Type: application/json Request format type. This should always be set as indicated to json only
Accept: application/json; charset=UTF-8 Response format type. This should always be set as indicated to json only Version:
Version: v API version v (defaults to 1, if not specified)
Is it possible that the API key, username and password are all contained with the header? The username and password are not used to set the API key.
You appear to be using the IG Labs REST Trading API; the documentation states there are two modes of authentication, depending on the API version you specify. The header documentation you quote is a general overview of the different headers that are involved. That section is not very useful to actually understanding how the authentication / authorization works for that API.
On the same page is a Authentication and authorisation section, and it is that section you need to study. This is where the two modes of authentication are described. There is also a separate examples section that explains how the two different modes work with concrete requests and responses shown.
To authenticate, you must first send a POST request to the /session route, with your API key present in the X-IG-API-KEY header. This is where you'll need the username and password. A valid username / password then gives you security tokens to be used for subsequent requests. For API versions 1 and 2 an additional field encryptedPassword can be included to state whether or not the password is plain text or encrypted; you only need this to satisfy IG Singapore login restrictions, and the field can safely be omitted.
For v1 and v2 requests, you get those tokens in the response headers, API version v3 gives you the required information in the body. The tokens have a limited lifetime. The v1/v2 tokens are valid for 6 hours, but are automatically extended up to 72 hours by making requests with those tokens, before you need to log in again. The v3 token is only valid for 60 seconds, but a separate refresh_token lets you get a new token by sending the refresh token to the /session/refresh-token endpoint.
V1 / v2 requests then use the tokens in the exact same headers the /session response uses them, so copy across the CST ("client session token") and the X-SECURITY-TOKEN headers and you are on your way.
V3 requests use the standard OAuth Authorization header, with method set to Bearer. Just construct that header with the access_token value from the oauthToken structure, and save the refresh_token for when the access_token has expired. The documentation also recommends you set the IG-ACCOUNT-ID header, to identify the account (the token identifies the client only).
I strongly recommend you use a requests.Session() object to make header handling simpler.
For versions v1 and v2 of the API, use:
import requests
url = 'https://website.com'
API_KEY = '.... your API key ....'
username = 'username'
password = 'password'
session = requests.Session()
# these are sent along for all requests
session.headers['X-IG-API-KEY'] = API_KEY
# not strictly needed, but the documentation recommends it.
session.headers['Accept'] = "application/json; charset=UTF-8"
# log in first, to get the tokens
response = session.post(
url + '/session',
json={'identifier': username, 'password': password},
headers={'VERSION': '2'},
)
response.raise_for_status() # if not a 2xx response, raise an exception
# copy across the v1/v2 tokens
session.headers['CST'] = response.headers['CST']
session.headers['X-SECURITY-TOKEN'] = response.headers['X-SECURITY-TOKEN']
Now you are set up to use session to continue to access the API.
For v3, the tokens live in the JSON body:
session = requests.Session()
# these are sent along for all requests
session.headers['X-IG-API-KEY'] = API_KEY
# log in first, to get the tokens
response = session.post(
url + '/session',
json={'identifier': username, 'password': password},
headers={'VERSION': '3'},
)
response.raise_for_status() # if not a 2xx response, raise an exception
response_data = response.json()
oauth_tokens = response_data['oauthToken']
session.headers['Authorization'] = 'Bearer ' + oauth_tokens['access_token']
session.headers['IG-ACCOUNT-ID'] = response_data['accountId']
Now you are set up to use session to continue to access the API until oauth._tokens['expires_in'] seconds have passed or a 401 response is given:
if response.status == 401 and response.json()['errorCode'] == 'error.security.oauth-token-invalid':
# refresh required, old token is done.
You need to then use a session.post(url + '/session/refresh-token', json={'refresh_token': oauth_tokens['refresh_token']}) to get a freshoauthToken` structure:
# refresh the access token
del session.headers['Authorization']
response = session.post(
url + '/session/refresh-token',
json={'refresh_token': oauth_tokens['refresh_token']},
headers={'VERSION': '1'},
)
response.raise_for_status() # if not a 2xx response, raise an exception
oauth_tokens = response.json()
session.headers['Authorization'] = 'Bearer ' + oauth_tokens['access_token']
Note that I keep sending the VERSION header with each individual request; their API uses version numbers per end point, so /session has 3 versions, but /session/refresh-token only has version 1, and you can't set VERSION to anything else or it'll break.
The V3 /session format may appear to be more cumbersome, but that version lets you refresh your access indefinitely, provided you do so within 10 minutes from your last token use; the V1 / V2 access tokens expire after 72 hours tops, whatever you do.
I am developing a Flask application which gives call to the REST service developed in Flask. The target REST service method is secured using Basic Authentication. I found that for this type of authentication, I have to use base64 encoding.
I am trying to pass the credentials to the service in this way:
headers = {'username': base64.b64encode(g.user['username'])}
response = requests.post('http://127.0.0.1:3000/api/v1.0/follower/' + username, headers=headers)
And at the service side, the username is fetched as :
user_name = request.authorization.username
However, the service is not able to authorize the provided credentials and it is throwing an error 401.
Is there any issue with the authorization at the service side and at the application side?
You are not creating a proper Basic Authorization header.
You'd have to call the header Authorization, and then set the header value to the string Basic <base64-of-username-and-password-separated-by-a-colon>.
If we assume an empty password, that would look like:
headers = {
'Authorization': 'Basic {}'.format(
base64.b64encode(
'{username}:{password}'.format(
username=g.user['username'],
password='')
)
),
}
See the Wikipedia description of the client side of the protocol.
However, there is no need to construct this manually, as requests will create the header for you when you pass in a username and password as a tuple to the auth keyword:
response = requests.post(
'http://127.0.0.1:3000/api/v1.0/follower/' + username,
auth=(g.user['username'], ''))
for me the working code was, but may have some error.
headers = {
'Authorization': 'Basic {}'.format(
base64.b64encode(
'{username}:{password}'.format(
username=g.user['username'],
password='').encode()
).decode()
)
}
After looking at several articles online, StackOverflow, and the Yelp Google Group, I've been unable to figure out the problem to an Invalid Signature error being produced from my Yelp API request.
Here is the exact error:
{'error': {'text': 'Signature was invalid', 'description': 'Invalid signature. Expected signature base string: [some text here with keys]}}
And the code I've written to go along with it:
import rauth
import time
def get_results():
#Obtain these from Yelp's manage access page
consumer_key = ''
consumer_secret = ''
token = ''
token_secret = ''
session = rauth.OAuth1Session(
consumer_key = consumer_key
,consumer_secret = consumer_secret
,access_token = token
,access_token_secret = token_secret)
request = session.get("http://api.yelp.com/v2/search?location=Boston&term=food")
#Transforms the JSON API response into a Python dictionary
data = request.json()
print(data)
session.close()
return data
if __name__=="__main__":
print(get_results())
So what exactly is causing this error? I've done some modifications prior to this attempt, and the previous attempts I made I got similar errors; except one time I only got a "Invalid Signature" error, with no "Expect signature base string" message
There are more steps to authentication as per the docs
Making a Request
Each request must contain the following OAuth protocol parameters:
OAuth Parameter Value
oauth_consumer_key Your OAuth consumer key (from Manage API Access).
oauth_token The access token obtained (from Manage API Access).
oauth_signature_method hmac-sha1
oauth_signature The generated request signature, signed with the oauth_token_secret obtained (from Manage API Access).
oauth_timestamp Timestamp for the request in seconds since the Unix epoch.
oauth_nonce A unique string randomly generated per request.
These parameters may be passed in the HTTP (Authorization) header as URL query keys or in the POST data. Generating the OAuth signature is done by applying the HMAC-SHA1 with the oauth_token_secret. You may view your OAuth consumer key at Manage API Access. OAuth libraries are available to generate these requests.
You are not passing oauth_timestamp which is required or applying the HMAC-SHA1 so you get an Invalid Signature error, it is clearly outlined in the docs above what you need to send.
There is also an actual python yelp api you could use but to make a request you can use the example below based on the request function from the example code: to make a request using oauth2 and requests:
import requests
import oauth2
def request(url, url_params=None):
consumer_key = ""
consumer_secret = ""
token = ""
token_secret =""
url_params = url_params or {}
consumer = oauth2.Consumer(consumer_key, consumer_secret)
oauth_request = oauth2.Request(method="GET", url=url, parameters=url_params)
oauth_request.update(
{
'oauth_nonce': oauth2.generate_nonce(),
'oauth_timestamp': oauth2.generate_timestamp(),
'oauth_token': token,
'oauth_consumer_key': consumer_key
}
)
token = oauth2.Token(token, token_secret)
oauth_request.sign_request(oauth2.SignatureMethod_HMAC_SHA1(), consumer, token)
signed_url = oauth_request.to_url()
print(u'Querying {0} ...'.format(url))
return requests.get(signed_url).json()
Which using your url outputs a whole load of json, the start of which is:
Querying http://api.yelp.com/v2/search?location=Boston&term=food ...
{'region': {'center': {'longitude': -71.05460875, 'latitude': 42.35028894954365}, 'span': {'latitude_delta': 0.0325510910039668, 'longitude_delta': 0.04668455000000904}}, 'total': 8351, 'businesses': [{'name': "Giacomo's Ristorante", 'url': 'http://www.yelp.com/biz/giacomos-ristorante-boston', 'mobile_url': 'http://m.yelp.com/biz/giacomos-ristorante-boston', 'rating_img_url_large': 'http://s3-media2.fl.yelpcdn.com/assets/2/www/img/ccf2b76faa2c/ico/stars/v1/stars_large_4.png', 'phone':
...............................................................
...............................................................
I am not sure if the api supports python 3 but the code above was tested with python3 and python2 and it works fine, to install oauth2 you can simple pip install oauth2 and the same with requests if you don't have it installed.
Another common issue is that the servers time is out of sync. On linux, one can run
sudo ntpdate -s time.nist.gov
I've been following the guide for Twitter's 3-legged oauth setup:
https://dev.twitter.com/docs/auth/implementing-sign-twitter
Step 1: Obtaining a request token
For their authentication, step 1 requires making a post request containing the base64 encoded public and secret key.
key = "CONSUMER_KEY"
secret = "CONSUMER_SECRET"
auth = base64.encodestring("%s:%s" % (key, secret)).replace("\n", "")
data = {}
data["grant_type"] = "client_credentials"
headers = {}
headers["Authorization"] = "Basic " + auth
headers["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-8"
headers["Accept-Encoding"] = "gzip"
response = requests.post("https://api.twitter.com/oauth2/token",
headers=headers, data=data)
This first request returns a valid response code 200 along with an access token. The response looks like this:
{u'access_token': u'AAAAAAAAAAAAAAAAAAAAAHHHHH... ...vncbi', u'token_type': u'bearer'}
Step 2: Redirecting the user
This is where the problem is occurring. According to the docs, the user then just needs to be redirected to the authorization url formatted like this:
https://api.twitter.com/oauth/authenticate?oauth_token=AAAAAAAAAAAAAAAAAAAAAHHHHH... ...vncbi
However when I get to this page I get an error message:
Is there something I missed? The access_token is being generated without an issue. I'm not sure if this message is showing up because I set something up incorrectly earlier in the process. I'm also not sure how to check if the oauth token has expired.
Actually, you have been following https://dev.twitter.com/docs/api/1.1/post/oauth2/token which is quite different, e.g. only used for public resources and not private like status updates. For the three step one checkout https://gist.github.com/ib-lundgren/4487236 or better yet http://twython.readthedocs.org/en/latest/
If you only want to access public resources like user timelines you can do so via the code below.
# OBS: If you want to look at per user details and make status updates
# you want the OAuth1 version. This is only for publicly available
# resources such as user timelines.
from requests_oauthlib import OAuth2Session
from oauthlib.oauth2 import BackendApplicationClient
# Credentials you get from registering a new application
client_id = '<the id you get from github>'
client_secret = '<the secret you get from github>'
# TODO remove
client_id = 'VVq5UniipB5nXFAqtTA'
client_secret = 'PlaHnaSDbeY4eYkv8XiqxS1nzGWyKoq5WYSNjdeaw'
client_id = 'I1Xi7fOeYnA9jabyvGUaZxY20'
client_secret = 'k5PZpINooRpjAfQccGwLUr2ZMEtRJtoX8cKaooHjKewWupxRBG'
token_url = 'https://api.twitter.com/oauth2/token'
client = BackendApplicationClient(client_id)
twitter = OAuth2Session(client_id, client=client)
headers = {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
}
twitter.fetch_token(token_url, headers=headers, auth=(client_id, client_secret))
# Only public resources available to this application-only clients.
r = twitter.get('https://api.twitter.com/1.1/statuses/user_timeline.json?count=100&screen_name=twitterapi')
print r.content
Make sure you use the github version of the libraries
pip install git+https://github.com/idan/oauthlib.git
pip install git+https://github.com/requests/requests-oauthlib.git