import requests
import os
import os.path
import sys
import importlib
import webbrowser
import base64
from datetime import datetime, timedelta
from requests.auth import HTTPBasicAuth
if os.path.isdir(os.path.join("../../..", "modules")):
module_dir = os.path.join("../../..", "modules")
else:
module_dir = os.path.join("../..", "modules")
module_path = os.path.abspath(module_dir)
if not module_path in sys.path:
sys.path.append(module_path)
import util
importlib.reload(util)
The following link is where Spotify provides instructions for the Authorization Code Flow, which is the flow covered in class to obtain delegated authority. We provide our own summary of Spotify-specific steps, and these presume that you have already created a Spotify account.
https://developer.spotify.com/documentation/web-api/quick-start/
Go to https://developer.spotify.com/dashboard/login, logging in to authenticate yourself as a client developer.
On the Dashboard, use the Create A Client ID
or, equivalently, click the My New App
template box.
In the "Create an App" dialog, give your app a name, perhaps something like cs181-<login>
, using your Denison login.
Click the checkboxes.
You should see a message that your application has been created, and be on a page dedicated to that application.
Click the Edit Settings
button and fill in the form as follows:
For Website
, I use https://datasystems.denison.edu
. You can use the same, or you can use some other web site that you are affiliated with. For some API providers, they check to see if the web site exists.
For Redirect URIs
, enter https://caileighmarshall.github.io/cs181project/
(including the trailing slash), and click Add
.
Click Save
to save your application settings edits.
On your application page, click Show Client Secret
, and leave the page up for the next set of steps.
In the same folder as this notebook, right-click on creds.json
and Open With
and select Editor
.
Verify that, for the "spotify"
entry, that the "redirect_uri"
entry has the value "https://caileighmarshall.github.io/cs181project/"
.
For the "client_id"
entry, copy and paste the 32 character Client ID
from your Application Dashboard as a string for that entry.
For the "client_secret"
entry, copy and paste the 32 character Client Secret
from your Application Dashboard as a string for that entry.
Save your changes to the creds.json
file. You may want to open it using the JSON navigator to make sure your JSON syntax is correct.
If the read_creds()
function invocation in the cell below is not successful, check your steps again.
creds = util.read_creds("spotify")
The following cells are going to be used to construct a URL. An HTTP GET would be performed at this URL by the Resource Owner, taking them to a Spotify
authentication and dialog, where they are asked if they approve of a particular application being able to perform certain categories of operations with their (the resource owner's) Spotify account. The user can either approve, or can deny the application the ability to do these operations. This corresponds to Step 2 of the OAuth dance.
In the cell below, we create a random string that will be used for the state parameter in the flow that follows. Note that we use util.update_creds()
after we update the in-memory creds
data structure, so that we do not have to manually edit the creds.json
file.
state = util.random_string()
creds['state'] = state
util.update_creds("spotify", creds)
The link below takes you to the Spotify documentation, which you should use to determine and set
in the cell that follows, providing the information needed to build the URL.
url = util.buildURL("", "")
paramsD = {
}
The cell below build the request itself, passing the url
and the paramsD
as part of assembling a GET
HTTP request.
s = requests.Session()
req = requests.Request('GET', url, params=paramsD)
prepped = s.prepare_request(req)
user_url = prepped.url
print(user_url)
After you run the next cell, and you (presumably) approve (i.e. you give delegated authority to the application), you will be redirected to the
redirect_uri
site. When you get there, copy the long string of characters, called acode
and displayed in red, and then come back to this notebook.
webbrowser.open(user_url)
For a regular application, the redirect_uri
would take the resource owner to a web server under the control of the app, which would take the code
provided, and put it in a database.
We simulate that with copy-and-paste. This part corresponds to Step 5 and Step 6 of the OAuth dance.
Copy the code between the string delimiters, and then execute the following cell.
code = ""
Our Equivalent of conveying, through the user, the code generated by the provider and approved by the user, and storing it in our "database" of our credentials file.
creds['code'] = code
util.update_creds("spotify", creds)
The code
is not enough. Although the app has the code, and it has been securely coveyed via an authenticated user, the application must securely authenticate and interact with the provider to obtain the token
, which is then sufficient for API requests.
In the same documentation page as before (), scroll down to their step 2, titled "Have your application request refresh and access tokens". This request and response correspond to Step 7 and Step 8 of the OAuth dance.
Using the documentation, fill in the parameters for buildURL
and for the POST body as a dictionary mapping parameters to values.
For specifying the client id and client secret, the documentation specifies two alternatives, in one, we assemble a header line that encodes the information, in the other, we include two more POST FORM parameter values. Since we are already specifying a dictionary for the POST FORM, we will use the second alternative.
url = util.buildURL("", "")
formD = {
}
The following uses your information above to assemble a POST HTTP request, and then looks at the url and body from the assembled request.
s = requests.Session()
req = requests.Request('POST', url, data=formD)
prepped = s.prepare_request(req)
prepped.url
prepped.body
Finally, we send the request to exchange the code for a token. If there was a problem with the assembled request, we print out the status code along with the body of the response, which often has information that can be used to help debug the problem.
response = s.send(prepped)
if response.status_code != 200:
print("Error. Status code:", response.status_code)
print(response.text)
print(response.json())
tokenD = response.json()
We then update our "database" with the access_token
and the other information needed to make data requests:
creds['token'] = tokenD['access_token']
creds['refresh'] = tokenD['refresh_token']
creds['scope'] = tokenD['scope']
expiration = datetime.now() + timedelta(seconds=tokenD['expires_in'])
creds['expires'] = str(expiration)
util.update_creds("spotify", creds)
In the following, we use a request to get the current user's profile, as documented here: https://developer.spotify.com/documentation/web-api/reference/users-profile/get-current-users-profile/
The following cell is like other API requests we have seen, we set up an endpoint and a location, and, in addition to any other request parameters we might need as Path Parameters, Query Parameters, Header Parameters, or POST-body Parameters, we include a Header parameter, where the header name is Authorization
, and the value is "Bearer <access_token>"
. While other providers that use OAuth2 for delegated authority must have a request parameter that conveys the access token, it could take a different form, like a query parameter, for instance.
creds = util.read_creds("spotify")
assert 'token' in creds
my_token = creds['token']
location = "api.spotify.com"
resource_path = "/v1/me"
url = buildURL(resource_path, location)
authHeader = "Bearer {}".format(my_token)
headerD = {"Authorization": authHeader}
response=requests.get(url, headers=headerD)
if response.status_code != 200:
print("Error. Status code:", response.status_code)
print(response.text)
response.json()
creds = read_creds("spotify")
assert 'token' in creds
assert 'refresh' in creds
location = "accounts.spotify.com"
resource_path = "/api/token"
url = buildURL(resource_path, location)
auth = HTTPBasicAuth(creds['client_id'], creds['client_secret'])
bodyD = {
'grant_type': 'refresh_token',
'refresh_token': creds['refresh']
}
response = requests.post(url, auth=auth, data=bodyD)
if response.status_code != 200:
print("Error. Status code:", response.status_code)
print(response.text)
response.json()
creds['token'] = tokenD['access_token']
del creds['refresh']
creds['scope'] = tokenD['scope']
expiration = datetime.now() + timedelta(seconds=tokenD['expires_in'])
creds['expires'] = str(expiration)
update_creds("spotify", creds)