DEV Community

loading...
Cover image for Explaining how OAuth works with Spotify as an example

Explaining how OAuth works with Spotify as an example

hmlon profile image Nikita Kholin Originally published at kholinlabs.com ・6 min read

Have you ever wondered what is OAuth, how it works, and why any more or less popular website implements it? In this article, we'll explore those questions and will write code in Python to use OAuth with Spotify.

What is it?

According to Wikipedia, OAuth is

OAuth is an open standard for access delegation, commonly used as a way for Internet users to grant websites or applications access to their information on other websites but without giving them the passwords.

Sounds pretty logical. Let's use a Spotify to make it more specific.

OAuth is commonly used as a way for Internet users to grant websites or applications (your website or application) access to their information (like their favorite artists, or ability to add a new artist to favorites) on other websites (Spotify) but without giving them the passwords.

One more thing. "OAuth is an open standard" which means that it's going to work about the same for all other websites (like Facebook, Twitter) that are implementing OAuth.

How it works

On the high level, you (are also called client, and the website you are trying to OAuth with is called provider), get a user's access token (which is essentially a password) and with it, you can use all the accesses that you've listed in a so-called "scope".

  1. A user clicks a link to the provider website that will request authorization
  2. The user sees an authorization screen

Authorization screen on Spotify

  1. The user accepts your scope
  2. The user is redirected to a "redirect URL" with an access token
  3. Now you can make requests for the data you need with the access token

But what do we need to make it work?

Create a "Client Application"

First of all, you need to create a "Client Application". Its main purpose is to know where to redirect the user on step #4.

Another purpose of the Client Application is to show the name and a brief description of your project on the authorization screen on step #2.

You can google find a place where to do this with a query something like "#{PROVIDER_NAME} oauth app".

Build a link to the "Provider"

First, you need to find how to build a link. Google can help us again. Just google "#{PROVIDER_NAME} oauth URL". You should find something like official documentation from the provider.

Once we've found how to build the link you would most likely need to use a couple parameters in it:

  • client_id — an identifier of your client application so that the provider knows where to redirect the user
  • redirect_uri — the famous redirect URL (the provider could use the one specified in the Client Application but usually you can enter multiple URLs there and this parameter is for the clarification)
  • scope — a list of accesses that your project needs (you can find scopes in the official documentation).

Please refer to the official documentation for exact names of the parameters listed above.

I will show what I would have to build in my case. Regarding the scope, I need user-read-email and user-follow-read to read user's email and what they follow. To start I would point to the URL where the server is running

provider_url = "https://accounts.spotify.com/authorize"

from urllib.parse import urlencode
params = urlencode({
    'client_id': 'MY_CLIENT_ID',
    'scope': ['user-read-email', 'user-follow-read'],
    'redirect_uri': 'http://127.0.0.1:5000/spotify/callback',
    'response_type': 'code'
})

url = provider_url + '?' + params
url
# => 'https://accounts.spotify.com/authorize?client_id=MY_CLIENT_ID&scope=%5B%27user-read-email%27%2C+%27user-follow-read%27%5D&redirect_uri=http%3A%2F%2F127.0.0.1%3A5000%2Fspotify%2Fcallback&response_type=code'

Create an endpoint for the redirect URL

On the step #4 user is redirected to the redirect URL. This URL is where an access token with user's info will come to.

When I've been building my link to the provider, I've used 'http://127.0.0.1:5000/spotify/callback' as a redirect URL. This link needs to point to an endpoint on whatever server you have.

Here's a quick example how to set up an endpoint in Flask

from flask import Flask
app = Flask(__name__)

@app.route("/spotify/callback")
def spotify_callback():
    return "You finally called me back!"

In the callback you've just created, you can do whatever you want with the user's data. You can create a user's profile on your project, send him an email or whatever. But if you want to get more data from this user later it's better to save a token that comes with the request to your database.

Testing whether it's working

Testing

Now that you got your access token, you can do the things that you've requested in the scope.

I've requested user-follow-read so now I can read what artists the user is following to notify when a new album comes out. Spotify has an endpoint to fetch user's followed artists so let's just use it

artists = []
all_artists_loaded = False
limit = 50
access_token = fetch_access_token() # your function to pull the token
                                    # from the place where you saved it
url = f"https://api.spotify.com/v1/me/following?type=artist&limit={limit}&access_token={access_token}"

while not all_artists_loaded:
    response = requests.get(url).json()['artists']
    current_request_artists = response['items']
    artists += current_request_artists
    if response['next']:
        url = response['next'] + f"&access_token={access_token}"
    else:
        all_artists_loaded = True

print(artists)

If you'd like more info on how to fetch latest albums you can read my previous article.

Save the refresh token too

Sadly, access token usually expires. But luckily we can request a new one with the refresh token that comes with the request token.

Once the access token is expired you need to send a request with the refresh token to the endpoint that is specified in the docs. We can do that with requests library in Python

refresh_token = pull_refresh_token() # your function to pull the token
                                     # from the place where you saved it
client_id = "YOUR_CLIENT_ID" # that you received once you created
                             # the Client Application
client_secret = "YOUR_CLIENT_SECRET" # that you received once you created
                                     # the Client Application too

refresh_url = "https://accounts.spotify.com/api/token" # from the docs

payload = {
    'refresh_token': refresh_token,
    'grant_type': 'refresh_token'
}

auth_header = base64.b64encode(six.text_type(client_id + ':' + client_secret).encode('ascii'))
headers = {'Authorization': 'Basic %s' % auth_header.decode('ascii')}

response = requests.post(refresh_url, data=payload, headers=headers)

token_info = response.json()

Source of the code above.

The URL (as well as possible other required parameters) have to be, once again, found in the official documentation.

Why everyone implements OAuth?

It's just convenient for the end-user. OAuth takes the burden of creating passwords or fake accounts from users and places it on developers. But now it shouldn't be too hard for you to use any provider and leverage the full power of users' data.

Don't invent a bicycle

Bicycle

There are always existing solutions to almost any problem you face. Always make sure to google before inventing the bicycle. If you need authentication you, there are already libraries for that. If you need to access some API, like a needed to use Spotify's, there are libraries for that too.

If you'd like to read about OAuth more in-depth there is an official guide that covers probably everything written in a very easy to read form.

Also, this is the fourth part of a series of articles about the MuN. Stay tuned for part 5. You can find the code of this project, as well as my other projects, on my GitHub page. Leave your comments down below and follow me if you liked this article.

Discussion (7)

pic
Editor guide
Collapse
lepture profile image
Hsiaoming Yang

Hi Nikita, thanks for your post.

Since you are using Flask as an example and the library you mentioned in "Don't invent a bicycle" is a Django app library, I would like to add a Flask wheel here.

github.com/lepture/authlib

The documentation for the Flask integration is docs.authlib.org/en/latest/client/...

Collapse
hmlon profile image
Nikita Kholin Author

Awesome!
Thanks for the library, I didn't know about it. I'll be sure to check it out.

Collapse
lepture profile image
Hsiaoming Yang

Actually, before Authlib. There were Flask-OAuth and Flask-OAuthlib. But both of them are deprecated now.

Collapse
temmyraharjo profile image
temmyraharjo

Great article. But I curious asking is the Scope is same with Security role? If same which one you prefer put all the sec-role in the OAuth or built in by yourself in the application?

Collapse
phlash909 profile image
Phil Ashby

In the access token itself, these are the same thing. How an application understands the token content and uses it is different for each app.

For example - we have an application that handles workflow profiles (think Jira ticket lifecycle) on a per-user basis. Some people can edit profiles, others can execute. The app needs to know who you are, so you get the right profile, and we use the scope to determine if you can edit or execute. What we don't do is try and put the whole profile into OAuth properties. Note that OAuth and the authentication system does not understand profiles at all, it simple associates the words 'edit' and 'execute' with user identities, the application applies meaning to those terms locally.

Collapse
temmyraharjo profile image
temmyraharjo

Because what I think when we put scope in OAuth. It means that everytime we define scope: create_user, read_user, update_user, delete_user (let's say we have big module). We need to retrieve from OAuth to process all that information which is not efficient.

I always thinking that OAuth only need to be use for getting the token and refresh token. While security role is defined in the application it self to process the business logi..

Thread Thread
phlash909 profile image
Phil Ashby

As usual - it depends :)

In our case, we have a need to share roles across a number of endpoints / APIs / applications in a single-sign-on environment. These roles are managed centrally through OAuth tokens. What the roles /mean/ when a specific application or endpoint receives them is defined locally (as I described in the example above).

If you have a single application, and it already has local permissions / role management capability, then you have little need to move that elsewhere. Indeed your driver for using OAuth is likely different too, typically such applications need to accept identity assertions from other environments such as Google or Facebook, whereas we are building an SSO platform for ourselves... YMMV!