OAuth2 with WebView and C++ call

I have been struggling with figuring out OAuth connection for an application I am trying to create for BB10.  There are examples for WebWorks and Native in the GitHub repositories, but they weren’t very straight forward.  And there were hints and posts that it could be done in a more simple method using WebView.  After looking at a bunch of different code, I finally figured it out and not it seems easy.  The key was to use the WebView for the initial call for OAuth to get a code to send for the final Access authentication and then use C++ to make a post call to get the access tokens.

WebView basically puts a web browser in your application, so you need to read information from the browser.  In my app, I checked to see if the url changed in the WebView.  And when the URL changed, I checked to see if the parameter “code” was included in the URL.  From there, I took the value and passed it into the C++ function.

Here is the basic Page that starts the call with a button to change the URL for the WebView.

import bb.cascades 1.0
import HelloWorldServices 1.0

Page {
	property string returnCode;
	property string consumerKey: "123456789abcdef";
	property string consumerSecret: "abcdef987654321";
	property string redirectURI: "http://mysite.com/oauth/";
	property int urlChange: 0;
	property bool codeReceived: false;
	property bool accessSent: false;
	property bool accessReceived: false;

	Container {
		Label {
			id: lHelloLabel
			// Localized text with the dynamic translation and locale updates support
			text: qsTr("Hello World") + Retranslate.onLocaleOrLanguageChanged
			textStyle.base: SystemDefaults.TextStyles.BigText
		}
		Button {
			id: btnGetHello
			text: "Get OAuth Access"

			onClicked: {
				startOAuth();
			}

			function startOAuth() {
				// open the authorzation url
				var url = 'https://secure.site.com/oauth2/authorize?'
				+ 'client_id=' + consumerKey
				+ '&response_type=code'
				+ '&redirect_uri=' + redirectURI;
				webWindow.url = url;
			}
		}

		ScrollView {
			Container {
				WebView {
					id: webWindow

					onUrlChanged: {
						var returnURL = webWindow.url.toString();
						// check to see if code has comeback before
						if(!codeReceived) {
							// Past initial login request
							if(returnURL.indexOf("code")) {
								// successful return
								var startTrim = returnURL.indexOf("code") + 5;
								var endTrim = returnURL.indexOf("&");

								if(endTrim > startTrim) {
									returnCode = returnURL.substring(startTrim, endTrim);
								}
								if(returnCode.length > 1) {
									// set code recieved to true
									codeReceived = true;
									if(!accessSent) {
										accessSent = true;
										// call Access function to complete OAuth
										getAccess();
									}
								}

							} else if(returnURL.indexOf("invalid_request") != -1) {
								taHelloResults2.setText("The request was malformed or missing parameters");
							} else if(returnURL.indexOf("unauthorized_client") != -1) {
								taHelloResults2.setText("The client is not authorized");
							} else if(returnURL.indexOf("access_denied") != -1) {
							  taHelloResults2.setText("The user denied the request for authorization");
								// do some sort of action about user not being able to access data
							} else if(returnURL.indexOf("unsupported_response_type") != -1) {
								taHelloResults2.setText("Meetup doesn't support the provided response_type");
							} else if(returnURL.indexOf("invalid_grant") != -1) {
								taHelloResults2.setText("The provided code was invalid");
							} else if(returnURL.indexOf("unsupported_response_type") != -1) {
								taHelloResults2.setText("Meetup does not support the provided grant type");
							} else {
								// other
								taHelloResults2.setText("Other data" + returnURL);
							}
						}

					}

					function getAccess() {
						var accessURL = String("https://secure.site.com/oauth2/access");
						hws.getAccess(accessURL, consumerKey, consumerSecret, returnCode, redirectURI, "authorization_code");
					}
				}
				TextArea {
					id: taHelloResults1
					minHeight: 50
				}

				TextArea {
					id: taHelloResults2
					minHeight: 50
				}
			}

		}
	 }
	attachedObjects: [
		HelloWorldServices {
		id: hws
    }
   ]
}

After using the Webview to get the initial request key, I call a C++ function to send an access request.

I pass in the url for the access request, my apps consumer key and secret, the returned code from the initial request, the redirect url (required for the OAuth I’m using, but does nothing) and the grant type.

void HelloWorldServices::getAccess(const QString &urlString, const QString &consumerKey, const QString &consumerSecret, const QString &code, const QString &redirectURI, const QString &grantType)
{
	QUrl mMultiPart;
	mMultiPart.setUrl(urlString);
	// post items for the Oauth Access I am using
	mMultiPart.addQueryItem("client_id", consumerKey);
	mMultiPart.addQueryItem("client_secret", consumerSecret);
	mMultiPart.addQueryItem("grant_type", grantType);
	mMultiPart.addQueryItem("redirect_uri", redirectURI);
	mMultiPart.addQueryItem("code", code);
	// blank outrequest
	QString outRequest = "";

	QNetworkAccessManager *networkManager = new QNetworkAccessManager(this);
	// Setting mutipart query to request
	QNetworkRequest request(mMultiPart);
	request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
	// ssl setup - works with or without
	QSslConfiguration config = request.sslConfiguration();
	config.setPeerVerifyMode(QSslSocket::VerifyNone);
	config.setProtocol(QSsl::TlsV1);
	request.setSslConfiguration(config);

	if (networkManager) {
		QNetworkReply *reply = networkManager->post(request, outRequest.toAscii());
		bool ok = connect(reply, SIGNAL(finished()), this, SLOT(onGetReply()));

		Q_ASSERT(ok);
		Q_UNUSED(ok);

		if (reply) {
			qDebug() << "Reply from server is " << reply->readBufferSize();
		}
	}
}

When the reply happens, then I can parse out the JSON response

void HelloWorldServices::onGetReply()
{
	QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
	QString response;

	if (reply)
	{
		if (reply->error() == QNetworkReply::NoError) {
			int available = reply->bytesAvailable();
			if (available > 0) {
				QByteArray buffer(reply->readAll());
				response = QString(buffer);
			} else {
				response = "Zero bytes in response";
			}
		} else {
			int httpStatus = reply->attribute(
			QNetworkRequest::HttpStatusCodeAttribute).toInt();
			response = "Error and the code is "
			+ reply->attribute(
			QNetworkRequest::HttpStatusCodeAttribute).toString()
			+ " And the Error string is :: "
			+ reply->errorString() + "\n";
		}
		reply->deleteLater();
	}  else  {
		response = "Response comes out to be null";
	}

	if ((response.trimmed().isEmpty()))
	{
		response = "Empty Response";
	}
	qDebug() << "Response String is :: " << response << " \n";

	if (!response.isNull())
	{
		qDebug() << "Start JSON";
		JsonDataAccess jda;
		QVariant mainList = jda.loadFromBuffer(response);
		qDebug() << mainList.toStringList();
		qDebug() << mainList.toMap().value("access_token");
		qDebug() << mainList.toMap().value("token_type");
		qDebug() << mainList.toMap().value("expires_in");
		qDebug() << mainList.toMap().value("refresh_token");
	}
}

About DeanLogic
Dean has been playing around with programming ever since his family got an IBM PC back in the early 80's. Things have changed since BASICA and Dean has dabbled in HTML, JavaScript, Action Script, Flex, Flash, PHP, C#, C++, J2ME and SQL. On this site Dean likes to share his adventures in coding. And since programming isn't enough of a time killer, Dean has also picked up the hobby of short film creation.

About DeanLogic

Dean has been playing around with programming ever since his family got an IBM PC back in the early 80's. Things have changed since BASICA and Dean has dabbled in HTML, JavaScript, Action Script, Flex, Flash, PHP, C#, C++, J2ME and SQL. On this site Dean likes to share his adventures in coding. And since programming isn't enough of a time killer, Dean has also picked up the hobby of short film creation.