I have promised to atendee of my OOW'16 session (Building Enterprise-Grade Mobile Apps with Oracle JET and Cordova [CON5731]) to post a blog about ADF BC REST security and integration with Oracle JET. This post is to demonstrate how we could reuse cookie ID generated by ADF BC REST Web session for REST requests from JET.
First thing first, here you can download source code - jet_adfbc_security.zip. This archive contains ADF BC REST application and JET source code (you need to copy it into your local JET application).
Take a look first into JET login form. This is where we collect username/password and call login function. One important detail - invalidComponentTracker, this allows to report required validation error, when user hits login button with empty username or password:
Here is the login function in JET. If there are no validation errors, it executes POST against ADF BC REST service custom method. In response we could return user info, preferences, etc. This is the only one request where we are using username/password. Key point of this request is to get JSESSIONID from ADF BC REST server, so we could use it for subsequent requests, without sending username/password again. This is similar concept to ADF Faces, it is also using JSESSIONID to track web user and HTTP session on the server. If login is successful, we are reading custom parameter from response with JSESSIONID value. JET router is updated to render different menu structure after login:
Custom response parameter is populated on the server in Filter class. On authentication request this parameter is set once:
ADF BC REST application is enabled with standard ADF Security:
This is how it works. Login form in JET:
Login is successful with redsam/welcome1 user. Two tabs are rendered - Home and People. Home tab displays chart with employees:
We should dive deeper and check what happens with REST communication. POST method in response gets custom parameter with JSESSIONID value, if authentication is successful based on Authorization header parameter:
Chart data in Home tab is retrieved through GET method and this method is not using Authorization header anymore. It calls REST method using JSESSIONID in URL. JESSIONID must be before URL parameters:
Home tab is implemented with JET chart component:
JSESSIONID is included into REST call URL through getURL method, which is referenced by JET collection:
People tab implements table with pagination support:
Same approach is applied in People tab. JSESSIONID is appended into URL through getURL method, before URL parameters:
People UI with paginated table in JET:
REST request URL contains JSESSIONID:
First thing first, here you can download source code - jet_adfbc_security.zip. This archive contains ADF BC REST application and JET source code (you need to copy it into your local JET application).
Take a look first into JET login form. This is where we collect username/password and call login function. One important detail - invalidComponentTracker, this allows to report required validation error, when user hits login button with empty username or password:
Here is the login function in JET. If there are no validation errors, it executes POST against ADF BC REST service custom method. In response we could return user info, preferences, etc. This is the only one request where we are using username/password. Key point of this request is to get JSESSIONID from ADF BC REST server, so we could use it for subsequent requests, without sending username/password again. This is similar concept to ADF Faces, it is also using JSESSIONID to track web user and HTTP session on the server. If login is successful, we are reading custom parameter from response with JSESSIONID value. JET router is updated to render different menu structure after login:
Custom response parameter is populated on the server in Filter class. On authentication request this parameter is set once:
ADF BC REST application is enabled with standard ADF Security:
This is how it works. Login form in JET:
Login is successful with redsam/welcome1 user. Two tabs are rendered - Home and People. Home tab displays chart with employees:
We should dive deeper and check what happens with REST communication. POST method in response gets custom parameter with JSESSIONID value, if authentication is successful based on Authorization header parameter:
Chart data in Home tab is retrieved through GET method and this method is not using Authorization header anymore. It calls REST method using JSESSIONID in URL. JESSIONID must be before URL parameters:
Home tab is implemented with JET chart component:
JSESSIONID is included into REST call URL through getURL method, which is referenced by JET collection:
People tab implements table with pagination support:
Same approach is applied in People tab. JSESSIONID is appended into URL through getURL method, before URL parameters:
People UI with paginated table in JET:
REST request URL contains JSESSIONID:
Thanks a lot. Was looking for it from long time.
ReplyDeleteHi Andrejus, We also tried to implement similar scenario but not using Oracle JET/ADF BC. We have Angular/Rest-Jersey(J2EE). Conceptually both are same to rely on JSESSIONID for subsequent request.
ReplyDeleteI see few differences though
1. For us JSESSIONID goes in next request ownwards automatically as a request header. Browser should take care of sending sessionid from subsequent request as in first request it got it using set-cookie. Server side J2EE is smart enough to use request header JSESSIONID or Authorization (whichever is present) to invoke service. I assume jSessionId in url might be a requirement of ADF-BC REST services. Is it?
2. What if user calls login service and then again go to login page and to relogin? We faced issue that Authorization header goes as usual but browser also appends JSESSIONID in request-header. JSESSIONID in request header takes preference and server provides access to user if even if username/password is wrong in second attempt. To avoid it we have to remove JSESSIONID from request header. It can only be removed by server code as cookie is HttpOnly. At this time we are invoking logout service, which clear JSESSIONID header before doing login and then we send subsequent login service call. In future we are planning to do clear header from login service only and then do a auto redirection to login2 service, which will do actual login.
3. If user keeps on working on client side for 10-15 min without calling a server side api, server side session may end and next call to server side will not identify JSESSIONID. To overcome this we need to keep pinging server after 5 min or so to keep server session awake. Angular has concept of idletimeout and keepalive, which takes care of counting end user's idle and if he is not idle, keep pinging server side to keep server session running. Do we have anything in JET like that?
Just thought of sharing our findings and issue with you.
Thanks
Sanjeev.
Hi Sanjeev,
ReplyDeleteThanks for follow up.
1. ADF BC REST is nothing to do with JSESSIONID. This is Web app deployed on WLS. I assume it would work with request header. But I prefer to pass it through URL, this way there is better control in my opinion.
2. This is why I prefer to control JSSIONID and pass it through URL, there are no issues like that. You can simply clear up client side variable with JSESSIONID
3. In ADF apps we usually set Web session timeout long enough, lets say 8 hours. This allows to keep session as long as it is needed. On logout in JET will close session. If browser is closed without logout - we also close session, with WebSocket.
Regards,
Andrejus
Thank you Andrejus for clarifying things. I believe your solution (url-rewriting) is better as it will work even if cookie is disabled and also you can handle url more effectively on client side as you said. We will try to change our solution in similar lines.
ReplyDeleteThanks
Sanjeev.
Sounds good. With REST it is easier to switch UI, so may be you also going to use JET in future :)
ReplyDeleteAndrejus
Hi Andrejus,
ReplyDeletewe are looking for a similar solution to integrate the Oracle Hybrid Jet application with SSO.
Can you provide us some options for the hybrid application how to handle the SSO part without using any paas components such as MCS or JCS-SX.
Thanks
Chetan
Hi,
ReplyDeleteYou could use exactly same approach with JET Hybrid as described in this blog, without any PaaS. It depends what kind of SSO integration you want to achieve?
Regards,
Andrejus
Hi Andrejus,
ReplyDeleteThanks for your good article.
I'm really interested about Oracle Jet but i have a question about server side session management. I have found this interesting discussion http://stackoverflow.com/questions/3105296/if-rest-applications-are-supposed-to-be-stateless-how-do-you-manage-sessions .
With your approch in a cluster enviroment we need to replicate server session between nodes if i understood well.
Do you think it's best solution?
Thanks to share your idea,
Gabriele
Hi,
ReplyDeleteRequest is done with JSESSIONID, this means load balancer should direct request processing to correct server node. This is the same as with Web application request.
Regards,
Andrejus
I downloaded jet_adfbc_security.zip, opened JETSecurityApp in NetBeans IDE. Right click on index.html to run it. The Chrome browser has url as http://localhost:8383/JETSecurityApp/index.html. On the upper left corner, I could see small rectangles, but I am unable to do anything there. Could you tell me how to run your code?
ReplyDeleteYou have ADFBCRestApp work space in the zip file, and you used JDeveloper to open the file. If I have existing REST web service provided by ADF BC, do I need to modify my ADF BC based on RESTSessionHandler.java and ADF Authentication and Authorization?
Hi,
ReplyDeleteTo run JET application, make sure to add JET libraries (copy them into application structure from JET distributable archive).
Regards,
Andrejus
Hello Anredjus,
ReplyDeletethank you so much,
I implemented your application and it's work perfectly.
Please do you have any solution to implement the security with LDAP(AIM 11g) not with jazn-data files.
Regards.
Hi Anredjus,
ReplyDeleteSay suppose I want the request header to be used in my custom method in ADF BC, how does the value get propagated from OJET to custom ADF BC method? I understand from your above example i can get the header value in the filter, but how can read it in my custom method?
In this case, I would define custom ADF BC REST method with parameter and set it through POST call in payload.
ReplyDeleteRegards,
Andrejus