I'm trying to implement a simple python client for Spotify api. According to the Spotify's Authorization Guide, the app can be authorized in two ways:
App Authorization: Spotify authorizes your app to access the Spotify Platform (APIs, SDKs and Widgets).
User Authorization: Spotify, as well as the user, grant your app permission to access and/or modify the user’s own data. For information about User Authentication, see User Authentication with OAuth 2.0. Calls to the Spotify Web API require authorization by your application user. To get that authorization, your application generates a call to the Spotify Accounts Service /authorize endpoint, passing along a list of the scopes for which access permission is sought.
CLIENT CREDENTIALS
My first attempt used the app authorization using the oauth2 module from Spotipy, because it requires no token passed, but only client id and client secret, which belong to the app developer.
client.py
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
class SpotifyWrapper(spotipy.Spotify):
def category_playlists(self, category, limit=50, offset=0):
return self._get('browse/categories/%s/playlists' % category,
limit=limit,
offset=offset)
def get_api_client():
# create a client authentication request
client_cred = SpotifyClientCredentials(
client_id=DevelopmentConfig.SPOTIFY_CLIENT_ID,
client_secret=DevelopmentConfig.SPOTIFY_CLIENT_SECRET
)
# create a spotify client with a bearer token,
# dynamically re-created if necessary
return SpotifyWrapper(auth=client_cred.get_access_token())
Then I would import and declare it here:
spotify_utilities.py
from app.resources.spotify.client import get_api_client
sp = get_api_client()
And in order to make requests and get user playlists, pass it like so:
def get_user_playlist(username, sp):
ids=[]
playlists = sp.user_playlists(username)
for playlist in playlists['items']:
ids.append(playlist['id'])
print("Name: {}, Number of songs: {}, Playlist ID: {} ".
format(playlist['name'].encode('utf8'),
playlist['tracks']['total'],
playlist['id']))
return ids
This works and will get user content, where the user is the app developer.
IMPLICIT FLOW
Now I want to move on to Implicit Flow, whereby the app asks ANY user who uses for access and scopes, and for that a token will be required.
Once I fetch the token using Javascript, I know I can use it to get user data hitting the API with simple requests:
GET_USER_PROFILE_ENDPOINT = 'https://api.spotify.com/v1/users/{user_id}'
GET_USER_PLAYLISTS_ENDPOINT = 'https://api.spotify.com/v1/users/{user_id}/playlists'
def get_user_profile(token, user_id):
url = GET_USER_PROFILE_ENDPOINT.format(id=user_id)
resp = requests.get(url, headers={"Authorization": "Bearer {}".format(token)})
print (len(resp.json()))
return resp.json()
def get_user_playlists(token, user_id):
url = GET_USER_PLAYLISTS_ENDPOINT..format(id=user_id)
resp = requests.get(url, headers={"Authorization": "Bearer {}".format(token)})
print (len(resp.json()))
return resp.json()
but in order to get (and change) user data first I need to use this token to fetch user ID.
Also, by the following example form Spotipy docs, user must provide his username at terminal:
if __name__ == '__main__':
if len(sys.argv) > 1:
username = sys.argv[1]
else:
print("Whoops, need your username!")
print("usage: python user_playlists.py [username]")
sys.exit()
token = util.prompt_for_user_token(username)
if token:
sp = spotipy.Spotify(auth=token)
playlists = sp.user_playlists(username)
After reading the docs from Spotify and Spotify, some things that are still not clear:
Is it possible to get this USER ID from passing the token only?
Must the app user necessarily provide his Spotify username via a form in a browser, besides authorizing the app when authentication is prompted?
Is it possible to tweak the wrapper above and implement a client which contemplates the parameters required for implicit flow? Would simply spotify = spotipy.Spotify(auth=token) work and get current usr data?
Also, by the following example form Spotipy docs, user must provide
his username at terminal:
That's because Spotipy caches tokens on disk. When no cache path is specified by the user the username simply gets appended to the files file extension as seen here. So the username specified is never being transmitted to any Spotify API endpoint.
1) Is it possible to get this USER ID from passing the token only?
Yes, using /v1/me instead of /v1/users/{user_id} will do exactly that assuming you are using an access token generated by Authorization Code flow or Implicit Grant flow.
2) Must the app user necessarily provide his Spotify username via a
form in a browser, besides authorizing the app when authentication is
prompted?
No, as seen in the first paragraph of my answer.
3) Is it possible to tweak the wrapper above and implement a client
which contemplates the parameters required for implicit flow? Would
simply spotify = spotipy.Spotify(auth=token) work and get current usr
data?
Spotipy seems to only use Authorization Code Flow right now. Due to you said you are
trying to implement a simple python client for Spotify api.
you should just implement Implicit Grant flow in your application. This has examples for all three Spotify authorization flows.
Related
I want to read outlook emails and save the attachments, and I'm using python-O365 module for that. The problem is this module requires account authentication in order to access outlook.
The workflow is in this way:
User accesses the function/api, which then uses predefined/hardcoded credentials to connect to the outlook account.
client = "XXXXXXXXXX-XXXX-XXXX-XXXXXXXXXXXXXXX"
secret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
credentials = (client, secret)
account = Account(credentials)
At this point the function provides a url in the console for the user to go visit and provide consent and asks the user to paste the authenticated url back in the console. Image below for reference.
The problem here is that I want this authentication to be done on UI, not in the console. Im pushing this API to a server, where it will be not possible for the user to access the console to get this url and paste back the authenticated url.
Is there a way to either skip this authentication on whole? Or atleast a way to redirect the user directly to this mentioned url in console and provide the authenticated url to console directly from UI?
I got my answer myself. Basically I imported the functions that are being used in O365 library into my code, and reworked them a bit to get what I wanted done.
Here it goes,
So by default on a GET request, this django API shows the link that user needs to visit, sign-in and provide consent.(client and secret are hardcoded).
consent_url, _ = con.get_authorization_url(**kwargs) This line of code is being used in oauth_authentication_flow function in O365 module to print out the consent_url in console. I used it to just return the consent_url to UI.
Once user sign-in and consent is provided and they copy the token-url to paste it back to console, result = con.request_token(token_url, **kwargs) this line of code is used in the same oauth_authentication_flow function in O365 module to check if access token and refresh token are successfully generated and stored.
So using a POST request, now a user can submit the token_url back to my django API to get access to O365 api without relying on console.
#api_view(['GET','POST'])
def setupMail(request,**kwargs):
client = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
credentials = (client, secret)
scopes=['basic', 'message_all']
global account
account = Account(credentials)
protocol = MSGraphProtocol()
con = O365.Connection(credentials, scopes=protocol.get_scopes_for(scopes),**kwargs)
if request.method == "GET":
consent_url, _ = con.get_authorization_url(**kwargs)
return Response('Visit the following url to give consent: ' + consent_url)
if request.method == "POST":
token_url = request.data.get('token')
if token_url:
result = con.request_token(token_url, **kwargs) # no need to pass state as the session is the same
if result:
return Response('Authentication Flow Completed. Oauth Access Token Stored. '
'You can now use the API.')
else:
return Response('Something go wrong. Please try again. ' + str(bool(result)))
else:
return Response('Authentication Flow aborted.')
else:
return Response('Bad Request',status=status.HTTP_400_BAD_REQUEST)
Please let me know if there are any security concerns that I need to be worried about.
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)
I created an python application that is using the Youtube api (so examples are in python, but doesn't really matter, the concepts should be the same). I managed to get it working where I can connect and make api calls. However, when I connect to the api, I have to define a flow that checks if a the credentials storage file exists. If it doesn't, then I have to manually sign in using the flow. After sign in the file (main.py-oauth2.json), is created with the token. I would like to be able to download the credentials without having to sign manually sign in. I was hoping there was a way to make a POST request for that token, like I have seen here, but I have been able to do this with Youtube api. Does anyone know how to implement the desired feature ?
main.py
flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE,
scope=YOUTUBE_UPLOAD_SCOPE,
message=MISSING_CLIENT_SECRETS_MESSAGE)
storage = Storage(OAUTH_CREDENTIALS)
credentials = storage.get()
if credentials is None or credentials.invalid:
# manual / UI login
credentials = run_flow(flow, storage, args)
Trying to use a google service account throws 401 errors on upload.
credentials = Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=YOUTUBE_UPLOAD_SCOPES)
if credentials is None or credentials.expired:
raise ValueError('Invalid credentials')
return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION,
credentials=credentials)
...
status, response = insert_request.next_chunk()
# <HttpError 401 "Unauthorized">
Evidence this can be done
The oauth2client.service_account.ServiceAccountCredentials class is
only used with OAuth 2.0 Service Accounts. No end-user is involved
for these server-to-server API calls, so you can create this object
directly without using a Flow object.
youtube api
Oauth flow docs
https://developers.google.com/identity/protocols/OAuth2#serviceaccount
The problem is that most YouTube data is private user data. Being that it is private user data you must be authenticated as a user who has access to the data in question in order to access it. To do that we use Oauth2 and login to our account and get an access token and a refresh token returned.
The access token can be used to request data from the Youtube Api, the refresh token can be used to request a new access token when ever the access token expires (After an hour)
Normally i would say that you should consider using a service account. Services accounts are dummy users who can be preconfigured with access to user data. Unfortunately the Youtube api does not support service accounts.
What you should be doing and what i have done a number of times in the past is to authenticate your code once. Get the refresh token and save it. In the future whenever you wish to run your application you simply use the refresh token to request a new access token and you will be able to access the api. You wont have to manually type your login and password and consent to the access anymore everything can be done in the background using the refesh token.
Note: You will need to watch it there are some cases that can cause a refresh token to expire but you shouldn't worry for the most part they are good for as long as you continue to use them regularly.
I am not a python dev but found this
from oauth2client import client, GOOGLE_TOKEN_URI
CLIENT_ID = "client_id"
CLIENT_SECRET = "client_secret"
REFRESH_TOKEN = "refresh_token"
credentials = client.OAuth2Credentials(
access_token = None,
client_id = CLIENT_ID,
client_secret = CLIENT_SECRET,
refresh_token = REFRESH_TOKEN,
token_expiry = None,
token_uri = GOOGLE_TOKEN_URI,
token_ id = None,
revoke_uri= None)
http = credentials.authorize(httplib2.Http())
I'm writing an app that interacts directly with my Box account. I need to perform all the operations listed in the Python SDK API. Sure enough, I'm trying to get over the authentication part. Given my client_id and client_secret, I have the following script:
#!/usr/bin/env python
import boxsdk
import requests
def store_tokens_callback(access_token, refresh_token):
# I don't know why this is never being called.
print access_token, refresh_token
oauth = boxsdk.OAuth2(
client_id="<my_client_id>",
client_secret="<client_secret>",
store_tokens=store_tokens_callback,
)
auth_url, csrf_token = oauth.get_authorization_url('http://127.0.0.1')
print auth_url
print csrf_token
r = requests.get(auth_url)
print r.text
client = boxsdk.Client(oauth)
I do get the auth_url:
https://app.box.com/api/oauth2/authorize?state=box_csrf_token_<csrf_tken>&response_type=code&client_id=<client_id>&redirect_uri=http%3A%2F%2F127.0.0.1
However, clicking in that URL all the time won't do it. I need a way to automate this authentication process, so I don't have to click at this button every time:
Sure enough, I could add a little Selenium task to get click on that button and get the url with the code, however I was looking for something easier ... between the lines.
A few questions:
How can I automate the auth process in the Box SDK?
Why isn't stoke_tokens_callback being called?
Check out the enterprise edition documentation, which allows you to interact via API calls only (no button clicking needed).
I had exactly the same requirement. The SDK will handle refreshing the access token when it expires, using the 60 day refresh token. The refresh token itself is also refreshed, meaning it's valid for yet another 60 days. Using the code below, having first "primed" the access and refresh tokens, will be good as long as the API is invoked at least once every 60 days. Follow the Box SDK instructions for obtaining your initial access and refresh tokens. You'll then have to install these Python modules:
pip.exe install keyring
pip.exe install boxsdk
Then use keyring.exe to prime the credential store:
keyring.exe set Box_Auth <AuthKey>
keyring.exe set Box_Ref <RefreshKey>
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'm new to Oauth. In the past for twitter applications written in Python i used python-oauth2 library to initialize client like this:
consumer = oauth.Consumer(key = CONSUMER_KEY, secret = CONSUMER_SECRET)
token = oauth.Token(key = ACCESS_KEY, secret = ACCESS_SECRET)
client = oauth.Client(consumer, token)
That was easy because twitter provides both CONSUMER and ACCESS keys and secrets. But now i need to do the same for tumblr. The problem is that tumblr provides only CONSUMER_KEY, CONSUMER_SECRET and these urls:
Request-token URL http://www.tumblr.com/oauth/request_token
Authorize URL http://www.tumblr.com/oauth/authorize
Access-token URL http://www.tumblr.com/oauth/access_token
Using this data how can i initialize client to access tumblr API?
UPD
jterrace suggested a code i tried to use before. The problem with it is oauth_callback. If i don't specify any, api returns error "No oauth_callback specified", but if i do specify some url like "http://example.com/oauthcb/" and follow the link http://www.tumblr.com/oauth/authorize?oauth_token=9ygTF..., then press Allow button, tumblr doesn't show any PIN code page, it immediately redirects to that callback url, which is useless since it's desktop application. Why PIN code isn't shown?
UPD 2
Tumblr API doesn't support PIN code authorization. Use xAuth instead - https://groups.google.com/group/tumblr-api/browse_thread/thread/857285e6a2b4268/15060607dc306c1d?lnk=gst&q=pin#15060607dc306c1d
First, import the oauth2 module and set up the service's URL and consumer information:
import oauth2
REQUEST_TOKEN_URL = 'http://www.tumblr.com/oauth/request_token'
AUTHORIZATION_URL = 'http://www.tumblr.com/oauth/authorize'
ACCESS_TOKEN_URL = 'http://www.tumblr.com/oauth/access_token'
CONSUMER_KEY = 'your_consumer_key'
CONSUMER_SECRET = 'your_consumer_secret'
consumer = oauth2.Consumer(CONSUMER_KEY, CONSUMER_SECRET)
client = oauth2.Client(consumer)
Step 1: Get a request token. This is a temporary token that is used for
having the user authorize an access token and to sign the request to obtain
said access token.
resp, content = client.request(REQUEST_TOKEN_URL, "GET")
request_token = dict(urlparse.parse_qsl(content))
print "Request Token:"
print " - oauth_token = %s" % request_token['oauth_token']
print " - oauth_token_secret = %s" % request_token['oauth_token_secret']
Step 2: Redirect to the provider. Since this is a CLI script we do not
redirect. In a web application you would redirect the user to the URL
below.
print "Go to the following link in your browser:"
print "%s?oauth_token=%s" % (AUTHORIZATION_URL, request_token['oauth_token'])
# After the user has granted access to you, the consumer, the provider will
# redirect you to whatever URL you have told them to redirect to. You can
# usually define this in the oauth_callback argument as well.
oauth_verifier = raw_input('What is the PIN? ')
Step 3: Once the consumer has redirected the user back to the oauth_callback
URL you can request the access token the user has approved. You use the
request token to sign this request. After this is done you throw away the
request token and use the access token returned. You should store this
access token somewhere safe, like a database, for future use.
token = oauth2.Token(request_token['oauth_token'], request_token['oauth_token_secret'])
token.set_verifier(oauth_verifier)
client = oauth2.Client(consumer, token)
resp, content = client.request(ACCESS_TOKEN_URL, "POST")
access_token = dict(urlparse.parse_qsl(content))
print "Access Token:"
print " - oauth_token = %s" % access_token['oauth_token']
print " - oauth_token_secret = %s" % access_token['oauth_token_secret']
print
Now that you have an access token, you can call protected methods with it.
EDIT: Turns out that tumblr does not support the PIN authorization method. Relevant post here.
If you just want to gain an access-token/secret to sign, you could just setup your callback URL as: http://localhost/blah
Fireup the CLI-app (after modifying the callback-url, secret and token ofcourse)
Follow the link in your browser
Allow app
View addressbar of the page you've been redirected to in the browser after allowing your app. It should look something like:
http://localhost/blah?oauth_token=xxxxxxxxxxxxxxxxxxxxxxxxxx0123456789ABCDEFGHIJKLMN&oauth_verifier=XXXXXXXXXXXXXXXXXXXXXXXXX0123456789abcdefghijklmn
Use the value of the query-parameter 'oauth_verifier' as your PIN:
XXXXXXXXXXXXXXXXXXXXXXXXX0123456789abcdefghijklmn
The CLI should print out your oauth-token and oauth-token-secret.
HTH! Got this working for tumblr in this way :)
Have a look at https://github.com/ToQoz/Pyblr
It uses oauth2 and urllib to provide a nice wrapper for exactly what you're trying to do.
It seems that what you're trying to do is access an OAuth 1 API with an OAuth 2 client.
See https://github.com/simplegeo/python-oauth2 and look for “three-legged OAuth example”.
had this problem with oauth2 and facebook.
#deepvanbinnen's answer lead me into the right direction.
facebook actually redirected to a page similar to this
'http://localhost/blah?code=AQAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX#_=_'
using then the ' AQAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX#_=_ as the PIN actually got me the access to the requested facebook account.
#jterrance's answer is good. However, realize it is a one _time_ manual procedure to get the access token. The access token is the key that you use for all subsequent API calls. (That's why he recommends saving the access token in a database.) The string referred to as 'PIN' (aka the verification key) is not necessarily a number. It can be a printable string in any form. That verification key is displayed on the authorization page at the URL printed in step 2 then pasted into the prompt for a the 'PIN'.