OAuth and API

I am trying to understand the concept of using an API with OAuth using my Xojo Desktop app (2013 r3.3).

I was reading about something similar here that uses OAuth with youtube but I am still confused about how the callback URI actually works.

The API I am trying work work with requires authorization and then the results are all in JSON.

I think the most confusing part is the OAuth authorization.

Once I log in, I send a REQUEST and get a RESPONSE. I am pretty sure I can work through that part. It’s the log in (and the callback URI) that confuses me.

Found something that helps a lot here

So I guess I just have to figure out how to read the callback string.

No need to re-invent the wheel. This is by far the best solution for OAuth: https://www.example-code.com/xojo/default.asp

So the redirect URI would actually be: http://localhost:3017/

Not necessarily, it could be myxojoapp://auth if you are on mobile
Look at this page https://www.oauth.com/oauth2-servers/redirect-uris/
Hopefully that will help somewhat.

Use an HTML viewer. Make the callback URI something you can predict, such as (as suggested) myxojoapp://auth and capture that in the HTMLviewer CancelLoad event. You can see a real-world example of this in https://github.com/thommcgrath/Beacon/blob/development/Project/Views/MiniBrowser.xojo_window - it has some Cocoa code you’ll need to work around a redirect detection issue on macOS.

I try to get the oAuth 2 login to work as well.

Using the Chilkat oAuth plugin for Xojo, the login actually works fine. But when I Log out, by removing the tokens, and want to login again I get an error saying that the port (or socket or something like that) is already in use. I am sure I have no instances of the previous oAuth session left.

Anybody any experience with this kind of behavior?

Hi Edwin,

Are you using oAuth 2 login for xojo web app.?

Hi Ronaldo, (Not the soccer player, right? :wink: )

No, I’m using it in a desktop project. But might use it for a web app in the future.
There is actually a web app I use as a middleware app to do some operations on the user’s behalf. The user will initialize the operation on the Desktop app. And since the tokens are “portable”, I can use those on the middleware app to handle upload operations.

But as far as login within a web app, no… not there yet.

Thanks Edwin. We’re both looking for oAuth 2 chilkat solution but mine is for web app. Good luck to us. :slight_smile:

He! Thanks. I’m sure you might be able to work with the chilkat plugin. Maybe not in a way as described in the CK’s examples. You just have to see if the target machine (where the app will be served at) is compatible.

But it starts at almost $300 right?

For future reference…

[quote=264804:@Loannis Kolliageorgas]Simple example with htmlview.
File[/quote]

[h]The process basically includes to first get an access token and once you have this token, then use it with any further api call.[/h]

Usually you will find details about how to get the oauth2 access-token in the REST API documentation of the webservice which you want to use.

Code could look something like this:

[code] Dim dic As New Xojo.Core.Dictionary

dic.Value(“grant_type”) = “password”
dic.Value(“username”) = “myusername”
dic.Value(“password”) = “mypassword”
dic.Value(“client_id”) = “AHxyKGssNdRz6bBrvqS3iREARka5FaTJ3kXRDFSS”
dic.Value(“client_secret”) = “QysGoB8jIMhCsCr1zTOKq1THhbTBohAyjD2PNg1i”

Dim json As Text

Try
json = Xojo.Data.GenerateJSON(dic)
Catch
Dim jError As xojo.Data.InvalidJSONException
MsgBox jError.Message + EndOfLine + EndOfLine + jError.Reason
Break
Return
End Try

// Convert Text to Memoryblock
Dim data As Xojo.Core.MemoryBlock = Xojo.Core.TextEncoding.UTF8.ConvertTextToData(json)

// Assign to the Request’s Content
Self.sock.SetRequestContent(data,“application/json”)

// Set the URL
Dim url As Text = “https://myserver.com/oauth/token/

// Send Asynchronous Request
Self.sock.Send(“POST”,url)[/code]

Then, in the PageReceived handler you may have code similar to this:

[code]If HTTPStatus = 200 Then

// TODO: check: required square brackets
buffer = “[” + buffer +"]"

Try
records = Xojo.Data.ParseJSON(buffer)

Dim rec As Xojo.Core.Dictionary = records(0)
If rec.Value("token_type") = "bearer" Then
  // save new access token in property
  Self.access_token = rec.Value("access_token")
  
  // Now go an make more API calls
  GoUseWebService
End If

Catch
Dim jError As xojo.Data.InvalidJSONException
MsgBox jError.Message + EndOfLine + EndOfLine + jError.Reason
Break
End Try

Else
MsgBox("Status: " + Str(HTTPStatus) + EndOfLine + EndOfLine + Content.ToString)
End If[/code]

Once we have the access-token, then we use it to authenticate subsequent api calls, for instance we may have a method 'GoUseWebService ’ with code like this:

[code] Self.sock.RequestHeader(“Authorization”)= "Bearer " + Self.access_token

// Set the URL with pagination parameter
Dim url As Text = “https://myserver.com/wp-json/wp/v2/media/?per_page=100

// Example for 5 pictures per page, show page 2 :
’ Dim url As Text = “https://myserver.com/wp-json/wp/v2/media/?per_page=5&page=2

// Send Asynchronous Request
Self.sock.Send(“GET”,url)[/code]

[quote=410425:@Oliver Osswald]

Then, in the PageReceived handler you may have code similar to this:

[code]If HTTPStatus = 200 Then

// TODO: check: required square brackets
buffer = “[” + buffer +"]"

Try
records = Xojo.Data.ParseJSON(buffer)

Dim rec As Xojo.Core.Dictionary = records(0)
If rec.Value("token_type") = "bearer" Then
  // save new access token in property
  Self.access_token = rec.Value("access_token")
  
  // Now go an make more API calls
  GoUseWebService
End If

Catch
Dim jError As xojo.Data.InvalidJSONException
MsgBox jError.Message + EndOfLine + EndOfLine + jError.Reason
Break
End Try

Else
MsgBox("Status: " + Str(HTTPStatus) + EndOfLine + EndOfLine + Content.ToString)
End If[/code][/quote]

Oliver - in the PageReceived handler, I need something like this, correct?:
Dim buffer As Text

And sorry, forgive the questions from someone who’s pretty darn new to the Xojo platform…

What about “records”? Should that be an array?

If you can give a more complete example, that would be greatly appreciated. I’m trying to integrate my nascent desktop app with Athena Healthcare’s EMR API.

Thank you!

Apologies for reviving the topic (and upcoming ramblings), but still having an issue. Basically, I configured a demo based on the Chilkat example. I can authenticate without issue. The problem I’ve run into is with regard to receiving the actual JSON/TOKEN.

For some reason the Chilkat WriteEntireTextFile didn’t like the ShellPath I passed it on the first run and it never saved the JSON. Unfortunately, now I’m “permanently” authenticated with no usable token. I’ve tried logging out, deleting cookies, etc. and reauthenticating but the process still short-circuits with AuthFlowState = 5 still saying “access granted”. Any thoughts on: 1) successfully resetting the process and 2) retrieving/storing a useful token (I may not use CK for file writes going forward)?

Hi @Michael_Williams ,

is this oAuth1 or oAuth2? What service is this for also? Could you post your code? I have written my own oAuth2 implementation for QuickBooks Online and I also use Chilkat oAuth2 for Gmail etc.
Thanks
Mike

I solved this for OS X if anyone is interested DM me. Also if you are using Windows make sure you use the HTMLViewer.Renderer = 1 on HTMLViewer.Open

#If TargetWindows Then
  Me.Renderer  = 1
#ElseIf TargetMacOS Then
  Me.Renderer  = 0
#endif

Due to problems with cefsubprocess.exe hanging around on Windows, I ended up abandoning the HTMLViewer entirely, which poses a real problem for OAuth. But there’s other advantages too:

  1. Password managers will work with the browser and not with HTMLViewer.
  2. Users are more likely to trust their browser than yours. Yes I know it’s pretty equally safe, but we’re talking about user perception here.
  3. Users can see the address bar to verify the site before entering their private info.

So how did I do it? Well the initial request goes to my server. My server has the OAuth secret key, so that never gets exposed, even in the client code. The initial request also contains an rsa public key. My server redirects the user to the provider’s login page, which completes and redirects back to my server’s receiver url. That script takes the received token and encrypts it using the public key previously supplied. The app the whole time has been polling the server every few seconds checking for the encrypted token, which it will be able to decrypt using its private key.

Letting the user use their own browser has solved a TON of problems, though it requires a bunch of infrastructure work to make happen.

If I do ever use HTMLViewer again, I will be using registry commands to turn on Edge rendering instead of Internet Exploder rendering. Nearly as good as WebKit, without all the Chrome embedded awfulness.

TLDR #1; OAuth2

TLDR #2; A client wants OAuth2 validation in order to launch a custom desktop application, so I simply need to auth, then retrieve info (e.g. email address and name) for placeholders within the launched app.

Hey @Mike_Cotrone, my code literally follows the Chilkat Example for OAuth2 (https://www.example-code.com/xojo/microsoft_graph_oauth2_access_token.asp). The only changes I make are to the credentials, of course, and instead of qa_data/tokens/microsoftGraph.json, I use an existing custom folder in SpecialFolder.ApplicationData. The json file doesn’t yet exist (as WriteEntireTextFile should create it), but otherwise there should be no complications.

Still I receive a “success” with connection, but an errno: 2 stating osErrorMessage: No such file or directory. FYI, the path it complains about is absolutely valid – even though I already know it is, I verified by copying it from the error successfully cding from the terminal to the location.

Thom - You are one of the most experienced folks in the community so I know you understand how all of this works way better than I. However I used the debugger to figure out a work around for OS X using the HTML Viewer. I also was about to use the users web browser and create a landing HTML5 page, but I did like the control aspect of the HTML Viewer. This workaround has worked solid for me for solving the OS X redirect issue. I am using regex to parse for my authorization code that I am receiving from in the case QuickBooks Online oAuth2. I basically go fishing in the DocumentComplete event looking for the auth code and my visual work around for the landing page is a canvas (AuthCompleteCanvas) that I have attached to the same window as HTMLViewer is popped up after a timer under HTMLViewer.TItleChanged is satisfied. I use a QuickBooks Online default callback URI so this page is hidden behind my Canvas.

HTH.
Mike

HTMLViewer.DocumentComplete:
Try
  // PARSE THE AUTHORIZATION CODE FROM THE CALLBACK URI
  Var callBackPatStr as String = "(?<=code=).+(?=\&state)"
  
  // GET AUTHORIZATION CODE
  Var qboOAuth2AuthorizationCodeStr as String  = Common.regexParseStr(url, callBackPatStr, True)
  
  If qboOAuth2AuthorizationCodeStr = "" Then
    Return
  Else
    QboAPI_Module.isOAuth2Valid = True
    // RESIZE WINDOW HEIGHT
    #If TargetWindows Then
      AuthCompleteCanvas.Visible = true
    #endif
    
    // SAVE OAUTH2 AUTHORIZATION IN MEMORY
    Common.qboOauth2AuthorizationCode = qboOAuth2AuthorizationCodeStr
    
    // SAVE OAUTH2 AUTHORIZATION TO DB
    Database_Module.syncSqlIteDb.qboAuth2TableAuthorizationCode_Update(qboOAuth2AuthorizationCodeStr)
    Common.qboOauth2AuthorizationCodeValid= True
    
    // GET NEW TOKEN: ACCESS / REFRESH TOKENS
    Var isGetNewAccessToken as Boolean =  Common.qboOAuth2AuthorizeSocket.callGetNewAccessToken(qboOauth2AuthorizationCode, qboRedirectUri, qboClientId, qboClientSecret)
    
  End If
  
  
Catch
  Var thisErrStr as String = "QBO oAuth2 Access & Refresh tokens failed bein returned by QOB."
  Call Common.writeLog(System.LogLevelCritical, "VersiSync API Call Exception: " + thisErrStr)
  
  
End Try




HTMLViewer.TitleChanged (WorkAround):
#If TargetMacOS Then
  htmlViewerCounter = htmlViewerCounter + 1
  if htmlViewerCounter = 5 Then
    AuthCompleteCanvas.Visible = true
    htmlViewerCounter = 0
  end if
#endif