JSDO with nodejs

Posted by Peter Willer on 06-Feb-2017 06:50

Hello,

we use JSDO to a PASOE with nodejs and this works fine, as long as we don't authenticate.Now we need some authentication to the pasoe using the form-oerealm authentication. Now nodejs first doesn't like the redirect (302) response from the Spring login servlet.

The idea is to read the generated JSESSIONID or other header from the OpenEdge Spring security in nodejs to do a second call to the known url to request the data.

Has someone already implemented a nodejs script with authentication through JSDO object in nodejs?

Regards

Peter

All Replies

Posted by whenshaw on 06-Feb-2017 10:30

Hi Peter,

I don't have a nodejs script that does what you are trying to do, but I do know that, in general, PASOE and the JSDO library try to avoid the redirect you see by using a Spring enhancement that will simply do the authentication check and return a 200 or 401 directly. Can you post your code that calls the JSDO library to do the authentication?

Regards,

Wayne

Posted by Peter Willer on 07-Feb-2017 06:44

Hello Wayne, below the sample:

var express = require('express'),

app = express(),

config = require('./config/config_mli'),

path = require('path'),

https = require('https'),

http = require('http'),

fs = require('fs'),

bodyParser = require('body-parser'),

cookieParser = require('cookie-parser'),

// multer = require('multer'),

cors = require('cors'),

   dummyData = require('./dummy.js'),

logger = require('morgan');

// require progress jsdo

XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;

require("./libs/progress.all.js");

process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';

var jsdosession, dataSource;    

var serviceURI = config.serviceURI,

   businessEntityName = config.businessEntityName,

   businessTask = "", // SUBSCRIPTION,ADDON,USER

   appDirectEvent = config.appDirectEvent, // ALL APPDIRECT EVENTS

   oData = dummyData[appDirectEvent], // LOADING APPDIRECT EVENT DUMMYDATA

 jsdoSettings = {

     serviceURI: serviceURI,

     catalogURIs: serviceURI + "/web/Catalog/" + businessEntityName

 },              

 promise;

jsdosession = new progress.data.Session();

jsdosession.authenticationModel = progress.data.Session.AUTH_TYPE_FORM;

console.log("connecting " + jsdoSettings.serviceURI);

console.log("login result: " + jsdosession.login( jsdoSettings.serviceURI, "test", "test", ""));      

console.log("STATUS:" + jsdosession.loginHttpStatus,"LOGIN RESULT:" + jsdosession.loginResult );

Posted by whenshaw on 07-Feb-2017 08:32

Thanks for the code. It looks like the login call is correct --- the code first sets the authenticationModel to Form, which should be the key to getting the behavior we want from PASOE. I will look into this a little bit more to see if I can figure out what's going on.

By the way, I see that the code is using an older API, progress.data.Session. We do still support that but are encouraging developers to use the newer progress.data.JSDOSession API instead. I have to admit, however, that it probably would not solve the problem you're seeing, since they use the same code to make the login request. Just thought I'd mention it. (Using it would require the code to be async, but I suspect from the fact that there's a variable named "promise" that you are doing that somewhere else anyway.)

Posted by egarcia on 07-Feb-2017 09:07

Hello,

Just in case this helps, here is a link to a sample program using the JSDO with native JavaScript Promises:

   community.progress.com/.../3190

The zip file includes an HTML file as well as a .js that be run from NodeJS.

The program uses the progress.data.JSDOSession API.

Cheers.

Posted by Akioma on 08-Feb-2017 13:12

Hi,

the sample code does not set an authenticationModel, so it uses anonymous (I think that is the default).

I would really like to see an example that uses form-based authentication. Or is this not supported...?

Thanks,

Mike

Posted by whenshaw on 09-Feb-2017 14:29

I think the answer might have to be that the JSDO library does not support Form-based authentication when running under NodeJS. I changed the sample posted by Edsel (egarcia) to use a Form-based Web application and got the same results reported in the original post. I ran into 2 problems. One can be easily worked around by modifying the XMLHttpRequest code, and one would be more difficult (if it can be done at all).

The simple problem is that the XMLHttpRequest implementation from driverdan/node-XMLHttpRequest used in Edsel's example always places "*/*" at the beginning of the list of values in the Accept request header, then adds any other values specified by the caller after that. (Don't know whether this is the same implementation used by Peter, the OP, in the code he shared). The JSDO's protocol with an OE back end is that the JSDO login code sets the request's Accept header to "application/json" and when the back end sees that, it does not do the standard 302 redirect for Form and instead does the authentication immediately and returns that result. I think what is happening is that the back end sees the "*/*" first and responds to that by sending the 302. The XMLHttpRequest object correctly detects the 302, gets the server's login page that it has been redirected to, and returns that, setting the XHR's status code to 200 because it successfully executed the redirect. The login request "succeeds", but there has not been any actual authentication. As a quick test,  I modified the XMLHttpRequest code so it doesn't include "*/*"  and the login really succeeded.

HOWEVER --- the subsequent request for the catalog (as well as a ping) failed with a 401 Unauthorized error. This is a bigger problem. Session information is passed between PASOE and a client in a JSESSIONID header --- PASOE initially sends the value in the response to the login. This value needs to be present in all subsequent requests to the server, to tell PASOE that the client has been authenticated. PASOE makes this header http-only by default for security reasons, so JavaScript can't get at it. The JSDO library, therefore, relies on the browser or other user agent it's running in to manage the JSESSIONID. My guess is that when you run in a NodeJS environment, you don't have the infrastructure that a browser has to handle the http-only headers.

I did a quick Web search to see whether other people have had this problem and whether there is a solution. I found a few hits having to do with session support in NodeJS but haven't read them thoroughly. There was nothing that seemed like an obvious answer to the problem (assuming that the JSESSIONID is indeed the problem).

Posted by Steve Boucher on 09-Feb-2017 15:10

Hi Wayne,
 
It’s getting more difficult to see out the windows, too much snow!
 
If I follow what you are saying correctly, is that I can assume that form authentication will not work for Tdriver tests.  This seems to imply that SSO wont work either.
 
True?
 
sb
 

Posted by egarcia on 09-Feb-2017 17:26

Hello,

I did some research and found that the setRequestHeader() API is expected to concatenate headers.

See the following links:

- msdn.microsoft.com/.../ms766589(v=vs.85).aspx:0:0

- www.w3.org/.../

The extra "*/*" at the beginning happens because driverdan/node-XMLHttpRequest defines a default Accept header and calls to setRequestHeader() then concatenate to the existing value.

Web browsers do not seem to have a default but just ensure that Accept: "*/*" is sent if it has not been set.

See:

- developer.mozilla.org/.../setRequestHeader:0:0

Perhaps, the code for setRequestHeader() and send() should be updated in the library.

The node-XMLHttpRequest does not handle cookies.

There is a library node-xmlhttprequest-cookie that adds support for cookies. It should be able to handle HttpOnly cookies since they are available in the code.

Links:

github.com/.../39

www.npmjs.com/.../xmlhttprequest-cookie

Perhaps, using this new library would allow the usage of JSESSIONID as a cookie.

Thanks.

Posted by egarcia on 10-Feb-2017 13:46

Hello,

I found that the xmlhttprequest-cookie library can be used to handle the JSESSIONID within NodeJS.

I had to do some changes to the XMLHttpRequest.js library and to the JSDO code to solve some changes in behavior.

Please let me know if you are interested on trying this out.

Thanks.

Posted by Peter Willer on 11-Feb-2017 04:01

Hello Edsel,

this sounds promising. I'm intersted of course.

Thanks

Peter

Posted by egarcia on 11-Feb-2017 05:49

Hello Peter,

Here is a way to try this out.

These changes should be consider experimental.
They have not been officially tested and the actual implementation may change.

Steps:

  • Unzip sample jsdo-promises-nodejs ( https://community.progress.com/community_groups/mobile/m/documents/3190 ).
  • Run npm install xmlhttprequest-cookie. This will install xmlhttprequest and xmlhttprequest-cookie.
  • Run node app.js to confirm that the code is working.
  • Update progress.jsdo.js in the sample to the code in the FormAuthNodeJS branch GitHub:
  • Update the XMLHttpRequest.js library:
    • Change definition for the XMLHttpRequest function to look like the following:

      exports.XMLHttpRequest = function XMLHttpRequest() {

    • Replace line "headers = defaultHeaders;" to the following code:

      headers = {};
      for (var name in defaultHeaders) {
      headers[name] = defaultHeaders[name];
      }
      // Remove Accept header so that it can be set via setRequestHeader()
      delete headers["Accept"];

  • Change the code in app.js to look like the following:
    • require("./pwrapper.js");

      xhrc = require("xmlhttprequest-cookie");
      XMLHttpRequest = xhrc.XMLHttpRequest;

      require("./progress.jsdo.js");
      require("./index.js");

  • Run node app.js to confirm that the code is working.
  • Change index.js to use your FORM-based service (serviceURI and catalogURI), include the authenticationModel property in the creation of the JSDOSession object and specify username and password.
    • jsdosession = new progress.data.JSDOSession({
      serviceURI: serviceURI,
      catalogURIs: catalogURI,
      authenticationModel: progress.data.Session.AUTH_TYPE_FORM
      });
      promise = jsdosession.login("<user>", "<password>");

  • Change the reference to the resource name in the creation of the JSDO instance and the code to handle the callback.

  • The application should run fine now using your FORM-based service.

Notes:

  • If you have issues with the native promises, you could use the Q library for the promise support ( www.npmjs.com/.../q ). You would need a "qwrapper.js" file that I can provide.
  • If you want to use BASIC authentication, you would need an implementation for btoa(). The base64.js library from knowledgecode github.com/.../base64.js provides this support.

Please let me know how it goes.

Enjoy,
Edsel

Posted by Peter Willer on 13-Jun-2017 07:27

Hello,

we had a working nodejs 2 pasoe implementation with 11.6.

Now we upgraded to 11.7 PASOE and the connection is not authorized now.

I found, that the form oerealm implementation in PASOE11.7 contains the new "session-fixation-protection", which I also set to "none" but without luck.

My next guess is that the change in tomcat 8.5 from a BIO to a NIO connector might be the case, but the BIO connector has been removed completely.

In the http logs from pasoe, I see the successfull call to the login:

xxx.xxx.xxx.xxx - - [13/Jun/2017:14:03:45 +0200] "GET /static/home.html?_ts=149735542-5333177220-1 HTTP/1.1" 401 57 19

xxx.xxx.xxx.xxx - - [13/Jun/2017:14:03:46 +0200] "POST /static/auth/j_spring_security_check HTTP/1.1" 200 42 286

xxx.xxx.xxx.xxx - - [13/Jun/2017:14:03:46 +0200] "GET /rest/_oeping?_ts=149735542-5333177220-2 HTTP/1.1" 401 57 2

xxx.xxx.xxx.xxx - - [13/Jun/2017:14:03:46 +0200] "GET /web/xxx.xxx.xxx.xxx HTTP/1.1" 401 57 1

I'm sure that the JSESSIONID from the j_spring_security_check is used in the next calls.

Has someone another idea what big changes between tomcat 7.0 and tomcat 8.5 could make such a difference?

Thank you

Regards

Peter Willer

Posted by Irfan on 13-Jun-2017 09:15

Hi Peter,

I would suggest to check the URL's again. The intercept URL's were tightened a little bit in 11.7. So if your request URL is like /rest/static/auth/login.jsp then it works fine in 11.6 but fails in 11.7. It is because the intercept URL's for 11.6 were to accept the URL's that has **/static/* but in 11.7 it only allows static/**.

I see that your URL has "/rest/_oeping". There is no service like "/rest/_oeping", the service URI for "_oeping" is "rest/_oepingService/_oeping". I expect something similar with your URL's.

Posted by Kevin Thorne on 22-Nov-2017 03:25

I am following the steps above (using Basic authentication), but have the error "promise.resolve is not a function".

Edsel mentioned - "If you have issues with the native promises, you could use the Q library for the promise support ( www.npmjs.com/.../q ). You would need a "qwrapper.js" file that I can provide."

Would you be able to provide the qwrapper.js to see if this will solve this issue? Thanks.

Posted by egarcia on 27-Nov-2017 05:58

Hello Kevin,

The error that you are seeing is because an issue with the wrapper code and recent changes to the library.

A call to promise.resolve() is performed before it is effectively assigned.

We have researched this scenario and found that the jQuery Deferred package now available in NPM provides the implementation that we need.

To use jquery-deferred, you can do the following:

- Install package jquery-deferred:

   npm install jquery-deferred

- Add code to allow usage of jquery-deferred as the Promise implementation. Your code in the app.js program in the sample would look like the following:

/*global XMLHttpRequest: true, btoa: true, $: true, localStorage: true, sessionStorage: true */
XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
btoa = require("base-64").encode;

var jQuery = require("jquery-deferred");
$ = function() {
};
$.Deferred = jQuery.Deferred;

var module = require("node-localstorage"),
    LocalStorage = module.LocalStorage;
localStorage = new LocalStorage('./scratch1');
sessionStorage = new LocalStorage('./scratch2');

require("./progress.jsdo.js");
require("./index.js");

We are looking into using ES6 Promises in a future release of the JSDO.

Considering that you are doing BASIC authentication, it looks like you have already added the code for btoa(). Just in case, the code above shows how to use the base-64 package as the implementation for it.

The sample above also shows the usage of node-localstorage as the implementation for localStorage and sessionStorage. You would need this with the latest version of the JSDO library 4.4.1: https://github.com/CloudDataObject/JSDO .

Please let me know if you have comments or questions.

Thank you and regards,

Edsel

P.S.:

The sample code above uses global to indicate the global symbols when using ESLint to validate your JavaScript code.

Here is a link to a sample configuration for ESLint:

https://community.progress.com/community_groups/openedge_kendo_ui_builder/w/openedgekendouibuilder/2936.how-to-enable-validation-of-javascript-code-for-a-kendo-ui-builder-app-using-visual-studio-code

For Node.js, you would just add "node": true to "env" in the .eslintrc.json.

This thread is closed