Spotify API get user saved tracks error 401 missing token - python

I'm building a discord bot with discord.py and I'm trying to get the spotify user saved tracks.
This is my auth def:
#classmethod
def get_token(self):
CLIENT_ID = 'myclientid'
CLIENT_SECRET = "myclientsecret"
SPOTIFY_TOKEN_URL = "https://accounts.spotify.com/api/token"
client_token = base64.b64encode("{}:{}".format(CLIENT_ID, CLIENT_SECRET).encode('UTF-8')).decode('ascii')
headers = {"Authorization": "Basic {}".format(client_token)}
payload = {"grant_type": "client_credentials"}
token_request = requests.post(SPOTIFY_TOKEN_URL, data=payload, headers=headers)
access_token = json.loads(token_request.text)["access_token"]
return access_token
This is my def where I try to get the user saved tracks:
#commands.command(name='splayfav', aliases=['splayfavorites', 'splaysavedtracks'], description="Command to see the info from spotify \n __Example:__ \u200b \u200b *!infos*")
async def play_saved_tracks_from_spotify(self, ctx):
token = self.get_token(ctx)
headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': f'Bearer {token}',
}
response = requests.get('https://api.spotify.com/v1/me/tracks', headers=headers)
print(response.text)
I get this error:
{
"error" : {
"status" : 401,
"message" : "Missing token"
}
}
But If I go in the Spotify API console and get manually a token and put it manually after bearer, then it works, obviously I'm trying to automate the process so I can't take it by hand every time. If I print the token it's actually a token(I mean it's not None or similar), but it's like if It is the wrong one.
How can I fix this problem?

This error message is stupid. What it should tell you is that your authorization token does not have the grant to access the user.
There's 2 different ways to authorize:
client credentials (as you do, can't access any user related stuff)
Authorization Code Flow, see: https://developer.spotify.com/documentation/general/guides/authorization/code-flow/
The second way requires a bit more setup in your app. Basically get redirected to spotify webpage, then the user selects allow for the required permissions, then you get back a code and with that you can get a token and the refresh token (this one is the interesting part).
Now this is quite a hustle to get through, but once you have the refresh token you can basically do almost the same call you do just to get a refreshed access token. (See "Request a refreshed Access Token" at the bottom of the page I linked above)
So with refresh token you can do:
POST https://accounts.spotify.com/api/token
HEADER:
Authorization: Basic $base64(clientId:clientSecret)
BODY Parameters (form url encoded)
grant_type: refresh_token
refresh_token: <your value here>
Use this improved access token and it will work.

Related

When I try to change volume with Spotify API, I get error code 403

When I try to change the volume, I get:
requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://api.spotify.com/v1/me/player/volume
I checked if my client_id and client_secret, everything seems fine. Also I have premium account.
import requests
from client_secrets import client_id, client_secret
AUTH_URL = 'https://accounts.spotify.com/api/token'
# POST
auth_response = requests.post(AUTH_URL, {
'grant_type': 'client_credentials',
"scope": "user-modify-playback-state",
'client_id': client_id,
'client_secret': client_secret,
})
# convert the response to JSON
auth_response_data = auth_response.json()
# save the access token
access_token = auth_response_data['access_token']
headers = {
'Authorization': 'Bearer {token}'.format(token=access_token)
}
data = {"volume_percent": 10}
response = requests.put("https://api.spotify.com/v1/me/player/volume",data=data, headers=headers)
response.raise_for_status()
Several authorization flows are detailed in their documentation but your request doesn't match any of them. If I understand correctly your app is just a server-side code without any frontend which means that the Client Credentials Flow is the one that best matches your need. In this flow you don't need to send the client ID and secret in the request body instead, you need to send them in the Authorization header in the following format: Basic <base64 encoded client_id:client_secret>.
Also, make sure to set the Content-Type header to application/x-www-form-urlencoded as stated in the documentation.
Otherwise, the rest seems fine.

Python Discord OAuth2 - Guild.Join (Joining a Guild)

Hi I'm trying to do a 'Authorize with Discord' that automatically joins the user to my guild.
I'm running a Flask application that handles all of these.
So far, here's my code:
def add_to_guild(access_token, userID, guildID):
url = f"{Oauth.discord_api_url}/guilds/{guildID}/members/{userID}"
headers = {
"Authorization" : f"Bearer {access_token}"
}
response = requests.post(url=url, headers=headers)
print(response.text)
However this doesn't work. I get a error message saying:
{"message": "405: Method Not Allowed", "code": 0}
On the OAuth2 docs, it says that i'm suppose to get a response of 201 if the user successfully joins, or 204 if user is already in the guild.
UPDATE 1:
I changed the method to requests.get and now I receive this error:
{"message": "401: Unauthorized", "code": 0}
UPDATE 2:
I created a Bot, invited it to my discord guild and successfully was able to get some information about my user in the guild. however once i left and try to run the link again, i got this error
{"message": "Unknown Member", "code": 10007}
UPDATE 3:
I changed the method to PUT and now I'm getting a Bad Request
def add_to_guild(access_token, userID):
url = f"{Oauth.discord_api_url}/guilds/{guildid i cant show}/members/{userID}"
botToken = "cant show also"
headers = {
"Authorization" : f"Bot {botToken}",
'Content-Type': 'application/json'
}
response = requests.put(url=url, headers=headers)
print(response.text)
To add a member, you should use requests.put(), and the Guilds Auth header needs to be a Bot token, so change to:
headers = {
"Authorization" : f"Bot {access_token}"
}
And make sure you are passing a Bot token. More at: https://discord.com/developers/docs/reference
The Authorization header must be a Bot token (belonging to the same
application used for authorization), and the bot must be a member of
the guild with CREATE_INSTANT_INVITE permission.
To get the member, you would use requests.get() without the bot header.
Update 3 put() is the way to go
However, you're still missing the JSON payload which must include the user access token received from the token exchange from a code grant.
data = {
"access_token" : access_token
}
Then fire your request with passing data to json, so
response = requests.put(url=url, json=data, headers=headers)
print(response.json)

Ebay Token and GetDealItems API Call Issue

I'm trying to access GetDealItems API and i have a nightmare to get this working. Even though I use the valid client_id','client_secret','ruName' i keep getting
{'error': 'invalid_client', 'error_description': 'client authentication failed'}
below is the ebay doc
https://developer.ebay.com/api-docs/buy/deal/resources/deal_item/methods/getDealItems
I guess i need to use this scope and url in my request
scopes:'https://api.ebay.com/oauth/api_scope/buy.deal' and the
url='https://api.ebay.com/buy/deal/v1/deal_item?limit=1000'
Please see below my Python code.
import requests, urllib, base64
def getAuthToken():
AppSettings = {
'client_id':'xxxx7c8ec878c-c80c4c69',
'client_secret':'xxxx56db-4b4a-97b4-fad2',
'ruName':'xxxxx-gscrcsrtj'}
authHeaderData = AppSettings['client_id'] + ':' + AppSettings['client_secret']
encodedAuthHeader = base64.b64encode(str.encode(authHeaderData))
headers = {
"Content-Type" : "application/x-www-form-urlencoded",
"Authorization" : "Bearer " + str(encodedAuthHeader)
}
body= {
"grant_type" : "client_credentials",
"redirect_uri" : AppSettings['ruName'],
"scope" : "https://api.ebay.com/oauth/api_scope/buy.deal"
}
data = urllib.parse.urlencode(body)
tokenURL = "https://api.ebay.com/identity/v1/oauth2/token"
response = requests.post(tokenURL, headers=headers, data=data)
return response.json()
response = getAuthToken()
print(response)
response['access_token'] #access keys as required
response['error_description'] #if errors
The most obvious problem I see is that you are using Bearer when you should be using Basic in your Authorization header.
Also, You are urlencoding your redirect_url when you pass the entire dictionary into urlencode. The docs say you are supposed to urlencode the scope parameter, but honestly, I never encode the scope and it still works for me.
Here is your modified code, with a few formatting changes:
import requests, urllib, base64
client_id='xxxx7c8ec878c-c80c4c69'
client_secret='xxxx56db-4b4a-97b4-fad2'
ruName='xxxxx-gscrcsrtj'
scope = urllib.parse.quote('https://api.ebay.com/oauth/api_scope/buy.deal')
def basic_token(key, secret):
return 'Basic ' + base64.b64encode((key + ':' + secret).encode()).decode()
def getAuthToken():
headers = {
"Content-Type" : "application/x-www-form-urlencoded",
"Authorization" : basic_token(client_id, client_secret)
}
data = (
'grant_type=client_credentials&'
f'redirect_uri={ruName}&'
f'scope={scope}'
)
tokenURL = "https://api.ebay.com/identity/v1/oauth2/token"
response = requests.post(tokenURL, headers=headers, data=data)
return response.json()
Update:
I think you need to use the authorization_code grant instead of client_credentials.
To use the authorization_code grant, modify your body to look like this:
data = (
'grant_type=authorization_code&'
f'code={authorization_code}&'
f'redirect_uri={ruName}&'
f'scope={scope}'
)
Also, you will need to follow your "redirect url" to get the actual authorization code. Execute the following:
redirect_url = (
'https://auth.ebay.com/oauth2/authorize?'
f'client_id={client_id}&'
f'response_type=code&'
f'redirect_uri={ruName}&'
f'scope={scope}'
)
print(redirect_url)
Copy/paste the url from stdout, follow the link, and click "accept", then you will be redirected to a url that looks like this:
https://signin.ebay.com/ws/eBayISAPI.dll?ThirdPartyAuthSucessFailure&isAuthSuccessful=true&code=<authorization code here>&expires_in=299
Copy/paste the authorization code into your code, then see if it works.
Realistically, eBay expects you to automate this within your application using a server, but it doesn't make sense for you to go through the trouble if you are building an app for personal use.
GetDealItems API uses client_credentials grant as evident from the docs
The authorization should be using client_id and secret as described in getting access tokens
curl -X POST 'https://api.ebay.com/identity/v1/oauth2/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Authorization: Basic UkVTVFRlc3...wZi1hOGZhLTI4MmY=' \
-d 'grant_type=client_credentials&scope=https%3A%2F%2Fapi.ebay.com%2Foauth%2Fapi_scope%2Fbuy.deal'
Note: if the error is client_authorization_failed, ensure that the correct Keyset for production is used for production. Also ensure that the keyset is also enabled for Oauth
Finally, you can use/refer to the official python SDK as well here
A simple way to check if the particular scope, in this case https://api.ebay.com/oauth/api_scope/buy.deal is even allowed for this app, is to navigate to the keyset page under Keys link and click on "Oauth scopes" under the keyset which details the scopes allowed and their purpose. If the application is once authorized for buy.deal, then the scope will appear there.
UPDATE
GetDeals API is restricted in Production for authorized applications only. Please reach out to the eBay developer program as provided in the link on the page below.
https://developer.ebay.com/api-docs/buy/deal/overview.html#API

Python requests to access OAUTH website content - SNL Finance

I've been banging my head up against the wall trying to retrieve content from the news source "SNL Finance". I have valid credentials, so in theory I should be able to programmatically access their news content.
In short, I've tried executing the below script but with no success:
s = requests.Session()
client_id = "..."
client_secret = "..."
token_url = "https://www.snl.com/SNL.Services.Security.Service/oauth/token"
protected_url = "https://www.snl.com/web/client?auth=inherit#news/article?id=40666532&KeyProductLinkType=14"
request_data = {
"client_id": client_id,
"client_secret": client_secret,
"scope": "https://www.snl.com",
"grant_type": "refresh_token",
"refresh_token": refresh_token
}
token_response = s.post(token_url, data=request_data)
### token response is in jwt format, including token_type, expires_in, scope, etc. ###
token = json.loads(token_response.text)["access_token"].split('>')[1].split('<')[0]
request_data["token"] = token
article = s.post(protected_url, headers=request_data)
Sadly, this fails. I end up with a 200 response, but it appears to just be the login page (honestly not entirely sure what I'm looking at).
For more background, I've included browser information as it populates throughout the authentication process:
Attempt to visit protected url, redirected to the below url (omitted snl base):
/web/client?auth=inherit&contextType=external&username=string&enablePersistentLogin=true&OverrideRetryLimit=0&SwitchGetToPostLimit=50000&contextValue=%2Foam&password=secure_string&challenge_url=https%3A%2F%2Fwww.snl.com%2Fweb%2Fclient%3Fauth%3Dinherit&request_id=-6149669210818920852&authn_try_count=0&locale=en_US&resource_url=https%253A%252F%252Fwww.snl.com%252FInteractiveX%252FDefault.aspx%253Ftarget%253Dnews%25252Farticle%25253Fid%25253D40666532%252526KeyProductLinkType%25253D14%2526SNL3%253D1
Request headers are shown here.
Upon entering login / password, a token is retrieved and the protected page loads.
Request cookies are as shown here.
Also, I'm a bit confused at to why the token value of SNL_OAUTH_TOKEN in the above link (second link) differs from what is shown in the jwt token response I receive from my script.
Any guidance here will be hugely appreciated. I'm also happy to send any other non-personal information that proves useful.
Thank you!
I ended up figuring this out. I underestimated Python's requests library.
It appears to be as easy as this:
# prep token request data
request_data = {
"client_id": client_id,
"client_secret": client_secret,
"scope": "https://www.snl.com",
"grant_type": "refresh_token",
"refresh_token": new_refresh_token
}
# post to token url with token credentials
# the request object stores the token response cookies
r1 = requests.post(token_url, data=request_data)
# post to protected url by setting cookies arg as cookies from previous response
r2 = requests.post(protected_url, cookies=r1.cookies)

Client-credentials don't work for powerBI REST API

I'm trying to implement the daemon authentication flow.
The following post request returns me an access token with the right scope:
p_url = 'https://login.microsoftonline.com/' + 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + '/oauth2/token'
data = { 'grant_type':'client_credentials',
'client_id': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'client_secret': 'L------------------------------------------=',
'resource':'https://analysis.windows.net/powerbi/api' }
r = requests.post(url=p_url, data=data)
I receive the following response
{
"access_token" : "ey------------"
"expires_on" : "1454857253",
"not_before" : "1454853353",
"expires_in" : "3600",
"token_type" : "Bearer",
"scope" : "Dashboard.Read.All Data.Alter_Any Dataset.Read.All Dataset.ReadWrite.All Report.Read.All",
"resource" : "https://analysis.windows.net/powerbi/api"
}
response = json.loads(r.text)
token = response['access_token']
headers = { 'Authorization': 'Bearer ' + token }
response = requests.get('https://api.powerbi.com/v1.0/myorg/datasets', headers=headers)
I use the endpoint from the applications "view endpoints" page.
However, when I attempt to get list of "datasets" I always receive 403. What might be missing from the acquire token process?
Your flow is a bit short. REST call for datasets seems OK, but as far as I know, you have to request the access token by authorization code, not client credentials alone.
1) Get authorization code
Depends on your flow, for website it will be received during logon process or call to /oauth2/authorize with { 'response_type':'code }
2) Get access token
With authorization code in a variable, you have to modify your request to include to authorization code, like this (grant_type and code fields are altered):
p_url = 'https://login.microsoftonline.com/' + 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + '/oauth2/token'
data = { 'grant_type':'authorization_code',
'client_id': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'client_secret': 'L------------------------------------------=',
'code': authorizationCodeForSingedInUser,
'resource':'https://analysis.windows.net/powerbi/api' }
r = requests.post(url=p_url, data=data)
Basically saying, you have to have a user account that accesses the Power BI resource. Your website (clientid + secret) are not authorized by itself. There must be a user involved.
What's more, afaik only "organization account" users can access power bi.
To be explicit and underline the main cause in this thread, post and comments: Power BI REST API can only be used via User with credentials with Organizational Account and be already signed in (activated) Power BI on Power BI portal. You can check if REST Api will work by checking if this user is able to use Power BI portal manually.

Categories

Resources