'Can't get OAuth 2.0 code in Qt app, but is seems to work in browser

I'm trying to configure communication with Google OAuth 2.0 in my Qt project. I was using this tutorial, although it seems to be a bit outdated. I configured everything in Google APIs site, used those data in Credentials/OAuth 2.0 Client IDs page:

enter image description here

Header file:

#ifndef GOOGLEAUTH_H
#define GOOGLEAUTH_H

#include <QObject>
#include <QOAuth2AuthorizationCodeFlow>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QUrl>
#include <QUrlQuery>
#include <QOAuthHttpServerReplyHandler>
#include <QDesktopServices>

class GoogleAuth : public QObject
{
    Q_OBJECT
public:
    explicit GoogleAuth(QObject *parent = nullptr);
    Q_INVOKABLE void click();    

private:
    QOAuth2AuthorizationCodeFlow google;
};

#endif // GOOGLEAUTH_H

Source file:

#include "GoogleAuthenticator.h"

GoogleAuth::GoogleAuth(QObject *parent) : QObject(parent)
{
    google.setScope("email");

    connect(&google, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, [=](QUrl url) {
        QUrlQuery query(url);

        query.addQueryItem("prompt", "consent");      
        query.addQueryItem("access_type", "offline");
        query.addQueryItem("nonce", "123456");
        url.setQuery(query);
        QDesktopServices::openUrl(url);
    });    

    google.setAuthorizationUrl(QUrl("https://accounts.google.com/o/oauth2/auth"));
    google.setAccessTokenUrl(QUrl("https://oauth2.googleapis.com/token"));
    google.setClientIdentifier("<client_id>");
    google.setClientIdentifierSharedKey("<client_secret>");

    auto replyHandler = new QOAuthHttpServerReplyHandler(5476, this);
    google.setReplyHandler(replyHandler);

    connect(&google, &QOAuth2AuthorizationCodeFlow::granted, [=]() {
        qDebug() << "Access Granted!";
    });
}

void GoogleAuth::click()
{
    google.grant();
}

When I run click() method browser opens, I can log into Google account and then it redirects me to the page with following message:

Callback received. Feel free to close this page.

I can even see the code I need in the URL (at least I think it is that code?).

The problem is I don't get proper callback in Qt app. When above page with callback message is loaded, I just get this output in Qt Creator:

qt.networkauth.oauth2: Unexpected call
qt.networkauth.replyhandler: Error transferring https://oauth2.googleapis.com/token - server replied: Bad Request

Outside the app it seems to be working, I checked it on this page.

How to solve that propblem? Can I get more detailed info about that bad request?



Solution 1:[1]

I have tested the example and strangely it does not work for the "email" scope, after analyzing the http request I found that the problem is the encoding of the "code" received and that it is used to obtain the token. So my solution is to correct that parameter and that can be done override the requestAccessToken() method or use setModifyParametersFunction(), in this case use the latter:

google.setModifyParametersFunction([](QAbstractOAuth::Stage stage,
                                   QVariantMap* parameters)
{
    if(stage == QAbstractOAuth::Stage::RequestingAccessToken){
        QByteArray code = parameters->value("code").toByteArray();
        (*parameters)["code"] = QUrl::fromPercentEncoding(code);
    }
});

Solution 2:[2]

The official tutorial is quite outdated, and it contains several problems like the one you're facing. Specifically, you need to URL-decode the login code that Google sends, but you need to intercept the parameter from inside Qt, which requires installing a parameter handler:

googleSSO.setModifyParametersFunction([](QAbstractOAuth::Stage loginStage, QVariantMap* parameters) {
   // Percent-decode the "code" parameter so Google can match it
   if (QAbstractOAuth::Stage::RequestingAccessToken == loginStage) {
      QByteArray code = parameters->value("code").toByteArray();
      (*parameters)["code"] = QUrl::fromPercentEncoding(code);
   }
});

We wrote about how to authenticate a Qt app with Google SSO, and all the issues that we faced, in our blog.

Solution 3:[3]

Answer of Pablo is outdated too. Working solution for now is:

void GoogleCloudAuth::modifyParametersFunction(QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* parameters)
{
    qDebug() << "modifyParametersFunction stage=" << static_cast<int>(stage);
    if (stage == QAbstractOAuth::Stage::RequestingAuthorization)
    {
        // The only way to get refresh_token from Google Cloud
        parameters->insert("access_type", "offline");
        parameters->insert("prompt", "consent");
    }
    else if (stage == QAbstractOAuth::Stage::RequestingAccessToken)
    {
        // Percent-decode the "code" parameter so Google can match it
        QByteArray code = parameters->value("code").toByteArray();
        parameters->replace("code", QUrl::fromPercentEncoding(code));
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 eyllanesc
Solution 2 Pablo Diaz
Solution 3