[GH-ISSUE #137] Automatic refresh of access token #84

Closed
opened 2026-02-27 19:25:57 +03:00 by kerem · 14 comments
Owner

Originally created by @henk23 on GitHub (Aug 12, 2018).
Original GitHub issue: https://github.com/jwilsson/spotify-web-api-php/issues/137

Hey, if I did not miss anything we have two options to notice that an access token refresh is needed:

  • An api call throws a SpotifyWebAPIException with message 'access token expired'.
    • Then we have to refresh the token and run the call again.
  • We check the expirationTime before the request.
    • Then we have to refresh the token and run the call.

To me this seems something that could be automated, because basically every developer using this library is going to need this at some point.
I thought of something like this:

class SpotifyWebAPI {
    public function __construct($request = null, $autoRefreshAccessToken = true) {
    // ...

I could try to make a PR if this would be desired functionality.

Originally created by @henk23 on GitHub (Aug 12, 2018). Original GitHub issue: https://github.com/jwilsson/spotify-web-api-php/issues/137 Hey, if I did not miss anything we have two options to notice that an access token refresh is needed: - An api call throws a SpotifyWebAPIException with message 'access token expired'. - Then we have to refresh the token and run the call again. - We check the expirationTime before the request. - Then we have to refresh the token and run the call. To me this seems something that could be automated, because basically every developer using this library is going to need this at some point. I thought of something like this: class SpotifyWebAPI { public function __construct($request = null, $autoRefreshAccessToken = true) { // ... I could try to make a PR if this would be desired functionality.
kerem 2026-02-27 19:25:57 +03:00
Author
Owner

@jwilsson commented on GitHub (Aug 14, 2018):

Hi!
Thanks for the suggestion!

I don't know how feasible this would be with the current architecture, given how separated the Session and SpotifyWebAPI classes are. They don't know anything about each other and since the user of the library is responsible for passing access token between them I don't know how this could be automated I'm afraid.

You're obviously more than welcome to try something out but I can't promise it'll be included in the library. My only suggestion if you do try to do something would be to check the expiration time before each call and refresh the token as needed.

<!-- gh-comment-id:412812292 --> @jwilsson commented on GitHub (Aug 14, 2018): Hi! Thanks for the suggestion! I don't know how feasible this would be with the current architecture, given how separated the `Session` and `SpotifyWebAPI` classes are. They don't know anything about each other and since the user of the library is responsible for passing access token between them I don't know how this could be automated I'm afraid. You're obviously more than welcome to try something out but I can't promise it'll be included in the library. My only suggestion if you do try to do something would be to check the expiration time before each call and refresh the token as needed.
Author
Owner

@henk23 commented on GitHub (Aug 23, 2018):

Hey. I tried and failed. ;)
I ended up writing a SpotifyService to wrap Session and SpotifyWebAPI, checking the expirationTime before returning a valid api instance. But since the storage of the accesToken, refreshToken and expirationTime depends on the particular project, I also don't see how to implement this in a general way to include in this repo.

<!-- gh-comment-id:415418073 --> @henk23 commented on GitHub (Aug 23, 2018): Hey. I tried and failed. ;) I ended up writing a `SpotifyService` to wrap `Session` and `SpotifyWebAPI`, checking the expirationTime before returning a valid api instance. But since the storage of the accesToken, refreshToken and expirationTime depends on the particular project, I also don't see how to implement this in a general way to include in this repo.
Author
Owner

@adamazad commented on GitHub (Aug 23, 2018):

I wrote a code to swap token 10 minutes before they expire (PHP run via
Node). I have a project that let's you schedule adding tracks to playlist
and this required fresh tokens at any given time.

On Thu, Aug 23, 2018, 4:41 PM henk23 notifications@github.com wrote:

Hey. I tried and failed. ;)
I ended up writing a SpotifyService to wrap Session and SpotifyWebAPI,
checking the expirationTime before returning a valid api instance. But
since the storage of the accesToken, refreshToken and expirationTime
depends on the particular project, I also don't see how to implement this
in a general way to include in this repo.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/jwilsson/spotify-web-api-php/issues/137#issuecomment-415418073,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AB1kSDQh5WKgymRGaLTnOeA3IO632tvOks5uTrETgaJpZM4V5fIV
.

<!-- gh-comment-id:415439574 --> @adamazad commented on GitHub (Aug 23, 2018): I wrote a code to swap token 10 minutes before they expire (PHP run via Node). I have a project that let's you schedule adding tracks to playlist and this required fresh tokens at any given time. On Thu, Aug 23, 2018, 4:41 PM henk23 <notifications@github.com> wrote: > Hey. I tried and failed. ;) > I ended up writing a SpotifyService to wrap Session and SpotifyWebAPI, > checking the expirationTime before returning a valid api instance. But > since the storage of the accesToken, refreshToken and expirationTime > depends on the particular project, I also don't see how to implement this > in a general way to include in this repo. > > — > You are receiving this because you are subscribed to this thread. > Reply to this email directly, view it on GitHub > <https://github.com/jwilsson/spotify-web-api-php/issues/137#issuecomment-415418073>, > or mute the thread > <https://github.com/notifications/unsubscribe-auth/AB1kSDQh5WKgymRGaLTnOeA3IO632tvOks5uTrETgaJpZM4V5fIV> > . >
Author
Owner

@SanderSander commented on GitHub (Aug 24, 2018):

It should be possible to detect that the access token is expired, and on that moment use the refresh token to obtain a new one.

From the docs:

Access tokens are deliberately set to expire after a short time, after which new tokens may be granted by supplying the refresh token originally obtained during the authorization code exchange.

Ofcourse it would be more efficient to check the expires_in, instead for waiting for the expired status code. but checking the expires_in isn't fail proof.

But when the SpotifyWebAPI has a method like setSession(Session $session) or by injecting it throug the constructor or even by extending the Request class to SessionBasedRequest it should be possible to auto refresh the access token. Even when a expire response is returned by spotify by handling that exception when session is set in the API.

I can make a PR and try some stuff because I really need this.

<!-- gh-comment-id:415789512 --> @SanderSander commented on GitHub (Aug 24, 2018): It should be possible to detect that the access token is expired, and on that moment use the refresh token to obtain a new one. From the docs: > Access tokens are deliberately set to expire after a short time, after which new tokens may be granted by supplying the refresh token originally obtained during the authorization code exchange. Ofcourse it would be more efficient to check the `expires_in`, instead for waiting for the expired status code. but checking the `expires_in` isn't fail proof. But when the `SpotifyWebAPI` has a method like `setSession(Session $session)` or by injecting it throug the constructor or even by extending the `Request` class to `SessionBasedRequest` it should be possible to auto refresh the access token. Even when a expire response is returned by spotify by handling that exception when `session` is set in the API. I can make a PR and try some stuff because I really need this.
Author
Owner

@SanderSander commented on GitHub (Aug 24, 2018):

Ok after some testing this worked for me, it isn't really nice YET. but it works without changing any code in the repository.

This could be added without breaking anything, and with tests and fixing the ugly stuff (Like setAPI) etc, so this feature is doable and requires a bit more effort.

In the constructor of SpotifyWebAPI it would be possible to do an instanceof SessionBasedRequest and than add a method getSession to the request object

class SessionBasedRequest extends Request
{
    private const TOKEN_EXPIRED = "The access token expired";

    /**
     * @var Session
     */
    private $session;

    /**
     * @var SpotifyWebAPI
     */
    private $api;

    public function __construct(Session $session)
    {
        $this->session = $session;
    }

    public function setAPI(SpotifyWebAPI $api)
    {
        $this->api = $api;
    }

    public function send($method, $url, $parameters = [], $headers = [])
    {
        try {
            return parent::send($method, $url, $parameters, $headers);
        }
        catch (SpotifyWebAPIException $exception)
        {
            if ($exception->getCode() === 401 && $exception->getMessage() === self::TOKEN_EXPIRED) {
                $this->session->refreshAccessToken($this->session->getRefreshToken());
                if($this->api !== null) {
                    $this->api->setAccessToken($this->session->getAccessToken());
                }
                // Re-attempt the request with our refreshed access token
                $headers['Authorization'] = 'Bearer ' . $this->session->getAccessToken();
                return parent::send($method, $url, $parameters, $headers);
            }
            else {
                throw $exception;
            }
        }
    }
}

And the bootstrap code from README

require 'vendor/autoload.php';

$session = new SpotifyWebAPI\Session(
    'CLIENT_ID',
    'CLIENT_SECRET',
    'REDIRECT_URI'
);

// When refresh token is loaded elsewhere
// $session->setRefreshToken('REFRESH_TOKEN');

$request = new SessionBasedRequest($session);
$api = new SpotifyWebAPI\SpotifyWebAPI($request);
$request->setAPI($api); // THE UGLY, but can be solved with minor changes 

if (isset($_GET['code'])) {
    $session->requestAccessToken($_GET['code']);
    $api->setAccessToken($session->getAccessToken());

    print_r($api->me());
} else {
    $options = [
        'scope' => [
            'user-read-email',
        ],
    ];

    header('Location: ' . $session->getAuthorizeUrl($options));
    die();
}

What do you guys think?

<!-- gh-comment-id:415819234 --> @SanderSander commented on GitHub (Aug 24, 2018): Ok after some testing this worked for me, it isn't really nice YET. but it works without changing any code in the repository. This could be added without breaking anything, and with tests and fixing the ugly stuff (Like `setAPI`) etc, so this feature is doable and requires a bit more effort. In the constructor of `SpotifyWebAPI` it would be possible to do an `instanceof SessionBasedRequest` and than add a method `getSession` to the request object ``` class SessionBasedRequest extends Request { private const TOKEN_EXPIRED = "The access token expired"; /** * @var Session */ private $session; /** * @var SpotifyWebAPI */ private $api; public function __construct(Session $session) { $this->session = $session; } public function setAPI(SpotifyWebAPI $api) { $this->api = $api; } public function send($method, $url, $parameters = [], $headers = []) { try { return parent::send($method, $url, $parameters, $headers); } catch (SpotifyWebAPIException $exception) { if ($exception->getCode() === 401 && $exception->getMessage() === self::TOKEN_EXPIRED) { $this->session->refreshAccessToken($this->session->getRefreshToken()); if($this->api !== null) { $this->api->setAccessToken($this->session->getAccessToken()); } // Re-attempt the request with our refreshed access token $headers['Authorization'] = 'Bearer ' . $this->session->getAccessToken(); return parent::send($method, $url, $parameters, $headers); } else { throw $exception; } } } } ``` And the bootstrap code from README ``` require 'vendor/autoload.php'; $session = new SpotifyWebAPI\Session( 'CLIENT_ID', 'CLIENT_SECRET', 'REDIRECT_URI' ); // When refresh token is loaded elsewhere // $session->setRefreshToken('REFRESH_TOKEN'); $request = new SessionBasedRequest($session); $api = new SpotifyWebAPI\SpotifyWebAPI($request); $request->setAPI($api); // THE UGLY, but can be solved with minor changes if (isset($_GET['code'])) { $session->requestAccessToken($_GET['code']); $api->setAccessToken($session->getAccessToken()); print_r($api->me()); } else { $options = [ 'scope' => [ 'user-read-email', ], ]; header('Location: ' . $session->getAuthorizeUrl($options)); die(); } ``` What do you guys think?
Author
Owner

@doekenorg commented on GitHub (Oct 4, 2018):

I have implemented roughly the same solution in my app.

I think it would be a great asset if we'd have a specific exception for this situation. I'd like the library to check against the exception message and throw a SpotifyWebApiExpiredTokenException.

Still think that wrapping your requests within a try catch is a valid way of dealing. I just don't like the extra check on the message.

<!-- gh-comment-id:427009318 --> @doekenorg commented on GitHub (Oct 4, 2018): I have implemented roughly the same solution in my app. I think it would be a great asset if we'd have a specific exception for this situation. I'd like the library to check against the exception message and throw a `SpotifyWebApiExpiredTokenException`. Still think that wrapping your requests within a try catch is a valid way of dealing. I just don't like the extra check on the message.
Author
Owner

@jwilsson commented on GitHub (Oct 7, 2018):

@doekenorg I think adding a SpotifyWebApiExpiredTokenException would open up the door for too many highly specific exceptions. To me, there's two "types" of exceptions, API ones (missing parameters, invalid track ID etc.) and Auth ones (invalid client, expired token etc.). I'm not that keen on adding anymore exceptions at this point.

<!-- gh-comment-id:427666400 --> @jwilsson commented on GitHub (Oct 7, 2018): @doekenorg I think adding a `SpotifyWebApiExpiredTokenException` would open up the door for too many highly specific exceptions. To me, there's two "types" of exceptions, API ones (missing parameters, invalid track ID etc.) and Auth ones (invalid client, expired token etc.). I'm not that keen on adding anymore exceptions at this point.
Author
Owner

@doekenorg commented on GitHub (Oct 7, 2018):

@jwilsson fair enough!

But since this is an auth error related, are you willing to add helpers to the Auth Exception? Something along the lines of hasInvalidCredentials() or hasExpiredToken()? These could be easy checks against the returned message. I just feel like checking against those messages in production code is like using $something === 'true'. It just doesn't feel right 😉, and it could break down easy when spotify decides to change it's exceptions messages. One update of this package would save everybody 😄 .

<!-- gh-comment-id:427669198 --> @doekenorg commented on GitHub (Oct 7, 2018): @jwilsson fair enough! But since this is an auth error related, are you willing to add helpers to the Auth Exception? Something along the lines of `hasInvalidCredentials()` or `hasExpiredToken()`? These could be easy checks against the returned message. I just feel like checking against those messages in production code is like using `$something === 'true'`. It just doesn't feel right 😉, and it could break down easy when spotify decides to change it's exceptions messages. One update of this package would save everybody 😄 .
Author
Owner

@jwilsson commented on GitHub (Oct 9, 2018):

@doekenorg Sure, sounds like it could be helpful! Wanna send a PR for it?

<!-- gh-comment-id:428287623 --> @jwilsson commented on GitHub (Oct 9, 2018): @doekenorg Sure, sounds like it could be helpful! Wanna send a PR for it?
Author
Owner

@doekenorg commented on GitHub (Oct 9, 2018):

Sure. Will do

<!-- gh-comment-id:428291817 --> @doekenorg commented on GitHub (Oct 9, 2018): Sure. Will do
Author
Owner

@lewislarsen commented on GitHub (Sep 1, 2019):

Any update on this?

<!-- gh-comment-id:526913869 --> @lewislarsen commented on GitHub (Sep 1, 2019): Any update on this?
Author
Owner

@bulgariamitko commented on GitHub (Sep 1, 2019):

my simple solution:

try { $api->me(); } catch (Exception $e) { if ($e->getMessage() == 'The access token expired') { // Fetch the refresh token from somewhere. A database for example. $session->refreshAccessToken($refreshToken); $accessToken = $session->getAccessToken(); // [Save this accessToken variable to the DB or Session // Set our new access token on the API wrapper and continue to use the API as usual $api->setAccessToken($accessToken); } echo '<pre>'; print_r($e->getMessage()); echo '</pre>'; echo '<pre>'; print_r('Refresh Token'); echo '</pre>'; }

<!-- gh-comment-id:526952586 --> @bulgariamitko commented on GitHub (Sep 1, 2019): my simple solution: `try { $api->me(); } catch (Exception $e) { if ($e->getMessage() == 'The access token expired') { // Fetch the refresh token from somewhere. A database for example. $session->refreshAccessToken($refreshToken); $accessToken = $session->getAccessToken(); // [Save this accessToken variable to the DB or Session // Set our new access token on the API wrapper and continue to use the API as usual $api->setAccessToken($accessToken); } echo '<pre>'; print_r($e->getMessage()); echo '</pre>'; echo '<pre>'; print_r('Refresh Token'); echo '</pre>'; } `
Author
Owner

@jwilsson commented on GitHub (Sep 28, 2019):

Hi everyone!
So after playing around with some of @SanderSander's ideas in https://github.com/jwilsson/spotify-web-api-php/issues/137#issuecomment-415789512 I've reached a solution that I'm pretty satisfied with and which I hope will solve everyone's needs.

I've pushed it to it's own branch and created a PR for it for anyone interested to try out (install it with composer require jwilsson/spotify-web-api-php:dev-auto-refresh). It's still missing tests and some more docs (only the basics to get you up and running are there atm).

But please try it out and let me know of any issues, thoughts, and/or ideas!

<!-- gh-comment-id:536160138 --> @jwilsson commented on GitHub (Sep 28, 2019): Hi everyone! So after playing around with some of @SanderSander's ideas in https://github.com/jwilsson/spotify-web-api-php/issues/137#issuecomment-415789512 I've reached a solution that I'm pretty satisfied with and which I hope will solve everyone's needs. I've pushed it to it's own branch and created [a PR for it](https://github.com/jwilsson/spotify-web-api-php/pull/167) for anyone interested to try out (install it with `composer require jwilsson/spotify-web-api-php:dev-auto-refresh`). It's still missing tests and some more docs (only the basics to get you up and running are there atm). But please try it out and let me know of any issues, thoughts, and/or ideas!
Author
Owner

@jwilsson commented on GitHub (Oct 19, 2019):

This has been released in 2.11.0! 🎉

Docs are available here.

<!-- gh-comment-id:544140131 --> @jwilsson commented on GitHub (Oct 19, 2019): This has been released in `2.11.0`! 🎉 Docs are available [here](https://github.com/jwilsson/spotify-web-api-php/blob/89afa2b5e1386beae80eaf53fe4eb37bed8b873d/docs/examples/automatically-refreshing-access-tokens.md).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/spotify-web-api-php#84
No description provided.