Authorisation guide

Introduction

Spotify uses access tokens to authorise requests to the Web API. They are strings of characters and ordinarily expire in an hour. There are two levels of authorisation: client and user authorisation, which yield different types of tokens and grant access to different parts of the API. Client tokens, also referred to as application tokens can be used to access generic endpoints, like retrieving albums or tracks. User tokens are required for endpoints that involve a specific user, like accessing saved tracks or manipulating playback, but they also grant access to generic endpoints.

The appropriate authorisation method for an application depends on the intended number of users. Simple scripts can be created relatively easily. Running a web server requires more effort, but it allows an application to serve any number of users. For examples of authorisation with application and user tokens in both server and scripting contexts, see the following:

See Authorisation for reference documentation and Spotify’s authorisation guide for more information about the underlying authentication procedures.

User authorisation

User authorisation involves a two-step process.

  • A user opens or is redirected to a specific URL in their browser and logs in to Spotify to authorise the application. Then the user is redirected to a set redirect URI with the address containing a code.

  • An access token is requested with the code from the redirection result.

This redirection and extraction of data is carried out with a web server. See this recipe on an Authenticating server for an example implementation. Spinning up a server can be replaced with some manual work, e.g. the user pasting information to a terminal. For example prompt_for_user_token() uses this manual way, but this also makes it unusable on a server.

Refreshing and saving tokens

Access tokens expire after an hour. Their expiration status can be determined via Token.is_expiring. How another valid token is obtained depends on the type of the token. Application tokens can simply be requested again. To avoid another authorisation when using user tokens, a refresh_token can be used to request new access tokens.

A refresh token is enough to request new user access tokens, making them a perfect candidate to save to a file or a database. It is valid until the user manually revokes it from Spotify. A new refresh token may also be returned when requesting a new access token.

In practice receiving new refresh tokens seems to be rare, but it is a possibility as laid out in the OAuth 2 specification. The spec states that if it does happen the old refresh token may be revoked, so the new recent token should be used instead. The most recent refresh token is always returned when refreshing a token, but any saved refresh tokens need to be updated too.

A means of saving and loading refresh tokens is provided in Configuration.

Token scopes

The scope of a user token represents its access rights to various endpoints. By default, tokens cannot be used, for example, to modify a user’s library. Scopes are set during user authorisation, and requesting additional scopes requires another authorisation.

However, the scope of a token can be gradually expanded across a number of authorisations. When authorising, the scope of the resulting token is determined by the scopes that were set at the start of authorisation. But when refreshing a token, the scope is expanded to cover all scopes that the user has granted a particular application in previous authorisations. This holds for refresh tokens of both new and previously generated tokens.

PKCE user authorisation

User authorisation can be performed using Proof Key for Code Exchange, an extension to ordinary user authorisation. It is intended for public clients, i.e. applications whose client secret might be exposed. As such, retrieving user tokens with it does not require a client secret.

However, PKCE introduces some restrictions to refreshing user tokens. The refresh token of a PKCE-authorised token can only be used to spawn the next token, after which it is invalidated, which means that if saved, the refresh token needs to be constantly updated to match the newest available refresh token. Still, all access tokens are valid for their full duration.

Security concerns

There are two main security concerns with authorisation, besides the obvious leaking of access tokens.

Using a state with user authorisation prevents cross-site request forgery (RFC 6749). A string can be sent as state on authorisation. When a user is redirected back, the same state should be returned as a query parameter. gen_state() is provided to generate random strings to send as state. parse_state_from_url() can then be used to extract the returned state. State is generated and checked automatically when using UserAuth.

A client secret might not always be safe and the redirect URI handler might be vulnerable (RFC 7636). The PKCE extension to user authorisation allows retrieving user tokens without a client secret, and provides an added layer of security with code challenges and verifiers. Challenges and verifiers are handled automatically when using UserAuth with PKCE enabled. Here’s a summary of the configuration requirements for each authorisation method.

Method

Client ID

Client secret

Redirect URI

Client authorisation

x

x

User authorisation

x

x

x

User token refresh

x

x

PKCE user authorisation

x

x

PKCE token refresh

x

Summary of authorisation methods

Authorisation methods are provided on three levels for both application and user access tokens. At the lowest level Credentials provides ordinary access tokens. RefreshingCredentials has similar functionality, but returns self-refreshing tokens. Finally, utility functions that wrap around RefreshingCredentials are provided for easy one-time authorisation.

A summary of all authorisation methods can be found below. See each method’s documentation in Authorisation for more details. References to classes Credentials and RefreshingCredentials are abbreviated as C and RC for brevity.

  • Creation: is the method for generating new tokens or refreshing them

  • Type: is the resulting token expiring or automatically refreshing

Application tokens

Creation

Type

Notes

Method

New

Expiring

C.request_client_token

Refresh

Expiring

1

C.refresh

New

Refreshing

RC.request_client_token

New

Refreshing

2

request_client_token()

User tokens

There are two variants of each user authorisation method. One uses ordinary OAuth 2 authorisation, the other uses the PKCE extension.

Creation

Type

Notes

Ordinary

PKCE variant

New

Expiring

3

C.request_user_token

C.request_pkce_token

Refresh

Expiring

C.refresh_user_token

C.request_pkce_token

Refresh

Expiring

1

C.refresh

C.refresh

New

Refreshing

3

RC.request_user_token

RC.request_pkce_token

Refresh

Refreshing

RC.refresh_user_token

RC.refresh_pkce_token

New

Refreshing

2, 4

prompt_for_user_token()

prompt_for_pkce_token()

Refresh

Refreshing

2

refresh_user_token()

refresh_pkce_token()

UserAuth can be used to simplify the implementation of user authorisation with either one of the credentials clients, with or without PKCE.

Notes

  1. This is a subject-agnostic refresh, fit for both app and user tokens with or without PKCE. For application tokens, a new token is returned.

  2. These functions wrap around RefreshingCredentials internally to provide a shorthand for one-off authorisation.

  3. These methods are paired with the first step of user authorisation: redirecting the user to a URL to login with Spotify.

  4. Requires manually pasting text to a terminal, is not usable on a server.

Authorising requests

The client provides two ways of authorising requests. Firstly an access token is accepted in the client’s constructor.

import tekore as tk

s = tk.Spotify(token)
a = s.artist(artist_id)

Secondly, the client can temporarily use another token for requests. This is particularly handy if only one client instance is created but there are many users, or if tokens are manually refreshed.

s = tk.Spotify(app_token)
a = s.artist(artist_id)

with s.token_as(user_token):
    user = s.current_user()