Search code examples
c++qtspotify

{"error":"invalid_client"} from Spotify Web Api when trying to refresh token


I'm a newbie in terms both on Qt and C++ but i was trying to play a bit with a spotify web api for uni project. Unfortunately I got stuck on posting my refresh token to api to get new access token. Every time i do that, i'm getting response {"error":"invalid_client"}. I've made sure both my client id and client secret are correct. I think there is an mistake somewhere in my header but i've tried few things and it didn't do much.

Here's my code:

void MainWindow::on_pushButton_2_clicked(){

    QNetworkAccessManager * manager = new QNetworkAccessManager(this);

    QUrl url("https://accounts.spotify.com/api/token");
    QNetworkRequest request(url);
    QString header = "my_client_id:my_client_secret";
    QByteArray ba;
    ba.append(header);
    ui->teOutput->appendPlainText(ba.toBase64());
    QString full_header= "Authorization: Basic " + ba.toBase64();
    //ui->teOutput->appendPlainText(full_header);
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
    request.setHeader(QNetworkRequest::ContentDispositionHeader, full_header);

    QUrlQuery params;
    params.addQueryItem("grant_type", "refresh_token");
    params.addQueryItem("refresh_token", "here_is_my_refresh_token");

    connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(onFinish(QNetworkReply *)));
    manager->post(request, params.query().toUtf8());
    //ui->teOutput->appendPlainText(params.query().toUtf8());
}

void MainWindow::onFinish(QNetworkReply *rep)
{
    QString data = rep->readAll();
    ui->teOutput->appendPlainText(data);
}

And here's how my request should look like (as suggested i've checked that using cUrl)

curl -X "POST" -H "Authorization: Basic xxxxxxxxxxx" -d grant_type=refresh_token -d refresh_token=xxxxxxxxxxxxx https://accounts.spotify.com/api/token

Solution

  • The command -H "Authorization: Basic xxxxxxxxxxx" is equivalent to:

    request.setRawHeader("Authorization", "Basic xxxxxxxxxxx"); 
    

    So you should use instead of request.setHeader(QNetworkRequest::ContentDispositionHeader, full_header);.


    Although it is not necessary to implement all the logic since the Spotify API uses OAuth2 that Qt implements in the Qt Network Authorization module.

    Only the QNetworkRequest header should be modified when requesting or refreshing the token since that is not part of the Oauth2 standard.

    constants.h

    #ifndef CONSTANTS_H
    #define CONSTANTS_H
    
    #include <QByteArray>
    #include <QUrl>
    
    namespace Constants {
        const QByteArray SPOTIFY_CLIENT_ID = "xxxxxxxx";
        const QByteArray SPOTIFY_CLIENT_SECRET = "yyyyyyyy";
    
        const QUrl SPOTIFY_AUTHORIZATION_URL = QUrl("https://accounts.spotify.com/authorize");
        const QUrl SPOTIFY_ACCESSTOKEN_URL = QUrl("https://accounts.spotify.com/api/token");
    }
    
    #endif // CONSTANTS_H
    

    networkaccessmanager.h

    #ifndef NETWORKACCESSMANAGER_H
    #define NETWORKACCESSMANAGER_H
    
    #include <QNetworkAccessManager>
    
    class NetworkAccessManager : public QNetworkAccessManager
    {
    public:
        using QNetworkAccessManager::QNetworkAccessManager;
    protected:
        QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData);
    };
    
    #endif // NETWORKACCESSMANAGER_H
    

    networkaccessmanager.cpp

    #include "networkaccessmanager.h"
    #include "constants.h"
    #include <QtGlobal>
    
    QNetworkReply *NetworkAccessManager::createRequest(QNetworkAccessManager::Operation op,
                                                       const QNetworkRequest &request,
                                                       QIODevice *outgoingData)
    {
        QNetworkRequest r(request);
        if(r.url() == Constants::SPOTIFY_ACCESSTOKEN_URL)
            r.setRawHeader("Authorization",
                           "Basic " +
                           QByteArray(Constants::SPOTIFY_CLIENT_ID + ":" + Constants::SPOTIFY_CLIENT_SECRET).toBase64());
        return QNetworkAccessManager::createRequest(op, r, outgoingData);
    }
    

    spotifywrapper.h

    #ifndef SPOTIFYWRAPPER_H
    #define SPOTIFYWRAPPER_H
    
    #include <QOAuth2AuthorizationCodeFlow>
    #include <QObject>
    
    class SpotifyWrapper : public QObject
    {
        Q_OBJECT
    public:
        explicit SpotifyWrapper(QObject *parent = nullptr);
        QNetworkReply *me();
    public Q_SLOTS:
        void grant();
    Q_SIGNALS:
        void authenticated();
    private:
        QOAuth2AuthorizationCodeFlow oauth2;
    };
    
    #endif // SPOTIFYWRAPPER_H
    

    spotifywrapper.cpp

    #include "spotifywrapper.h"
    #include "networkaccessmanager.h"
    #include "constants.h"
    
    #include <QDesktopServices>
    #include <QOAuthHttpServerReplyHandler>
    #include <QUrlQuery>
    
    SpotifyWrapper::SpotifyWrapper(QObject *parent) : QObject(parent)
    {
        QOAuthHttpServerReplyHandler * replyHandler = new QOAuthHttpServerReplyHandler(1337, this);
        replyHandler->setCallbackPath("callback");
        oauth2.setNetworkAccessManager(new NetworkAccessManager(this));
        oauth2.setReplyHandler(replyHandler);
        oauth2.setAuthorizationUrl(Constants::SPOTIFY_AUTHORIZATION_URL);
        oauth2.setAccessTokenUrl(Constants::SPOTIFY_ACCESSTOKEN_URL);
        oauth2.setClientIdentifier(Constants::SPOTIFY_CLIENT_ID);
        oauth2.setScope("user-read-private user-read-email");
        oauth2.setState("34fFs29kd09");
        connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser,
                &QDesktopServices::openUrl);
        connect(&oauth2, &QOAuth2AuthorizationCodeFlow::statusChanged, [=](
                QAbstractOAuth::Status status) {
            if (status == QAbstractOAuth::Status::Granted){
                emit authenticated();
            }
        });
    }
    
    void SpotifyWrapper::grant()
    {
        oauth2.grant();
    }
    
    QNetworkReply *SpotifyWrapper::me()
    {
        return oauth2.get(QUrl("https://api.spotify.com/v1/me"));
    }
    

    main.cpp

    #include "spotifywrapper.h"
    
    #include <QNetworkReply>
    #include <QGuiApplication>
    #include <QTimer>
    
    int main(int argc, char *argv[])
    {
        QGuiApplication a(argc, argv);
        SpotifyWrapper wrapper;
        wrapper.grant();
        QObject::connect(&wrapper, &SpotifyWrapper::authenticated, [&wrapper](){
            qDebug() << "authenticated";
            QNetworkReply *reply = wrapper.me();
            QObject::connect(reply, &QNetworkReply::finished, [=]() {
                    reply->deleteLater();
                    if (reply->error() != QNetworkReply::NoError) {
                        qDebug() << reply->errorString();
                        return;
                    }
                    qDebug() << reply->readAll();
                    QTimer::singleShot(1000, &QCoreApplication::quit);
            });
        });
        return a.exec();
    }
    

    You must also set as Redirect URIs in the SETTINGS of your application to "http://127.0.0.1:1337/callback":

    enter image description here

    The full example can be found here