JSDO with nodejs - Forum - Mobile - Progress Community

JSDO with nodejs

 Forum

JSDO with nodejs

This question is not answered

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
  • 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

  • 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 );

  • 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.)

  • 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.

  • 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

  • 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).

  • 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
     
  • 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.

  • 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.

  • Hello Edsel,

    this sounds promising. I'm intersted of course.

    Thanks

    Peter

  • 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

  • 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

  • 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.

    Regards,

    Irfan

  • 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.