I'm trying to use and understand the use of LTPA security in worklight and the propagation of the LTPA cookie.
I'm able to authenticate agains the WAS and using a sniffer I can see that worklight returns me the LtpaToken2 cookie but when I invoke the HTTP Adapter, that invokes a service in other WAS in the same machine as the Worklight server, that adapter does not propagate the cookies.
I think I have set the right configuration. (At the end)
Is it possible to configure worklight server for automatically propagate the LTPA token from the app to the adapters and from the adapters to the final service?
If it is not possible to do it automatically how can I retrieve the Ltpa cookie inside the adapter code for add it to the headers parameter of the WL.Server.invokeHTTP() method.
This is my security configuration:
For it work I have had to add the login.html by hand in the customized war generated in worklight studio.
Application-descriptor:
<ipad bundleId="xxxx" securityTest="BPMApp-strong-mobile-securityTest" version="1.0">
Adapter-descriptor:
<procedure connectAs="endUser" name="getRest" securityTest="BPMAdapter-securityTest"/>
Security configuration:
<realm loginModule="WASLTPAModule" name="BPMAuthRealm">
<className>com.worklight.core.auth.ext.WebSphereFormBasedAuthenticator</className>
<parameter name="login-page" value="/login.html"/>
<parameter name="error-page" value="/login.html"/>
<parameter name="cookie-name" value="LtpaToken2"/>
</realm>
<loginModule name="WASLTPAModule" canBeResourceLogin="true" isIdentityAssociationKey="false">
<className>com.worklight.core.auth.ext.WebSphereLoginModule</className>
</loginModule>
<mobileSecurityTest name="BPMApp-strong-mobile-securityTest">
<testUser realm="BPMAuthRealm"/>
<testDeviceId provisioningType="none"/>
</mobileSecurityTest>
<customSecurityTest name="BPMAdapter-securityTest">
<test isInternalUserID="true" realm="BPMAuthRealm" isInternalDeviceID="true"/>
</customSecurityTest>
Thank you.
I believe this is what you're looking for:
function getCurrentUser() {
path = '/snoop';
var attributes = WL.Server.getActiveUser().attributes;
var token = "LtpaToken=" + attributes.get('LtpaToken');
var input = {
method : 'get',
returnedContentType : 'html',
headers: {"Cookie": token},
path : path
};
return WL.Server.invokeHttp(input);
}
This code snipped is from 5.0.3, so I think the syntax may have changed for getting the token from the attributes object in newer versions.
You may need to change:
var token = "LtpaToken=" + attributes.get('LtpaToken');
to:
var token = "LtpaToken=" + attributes['LtpaToken'];
But this is the idea. The adapter is not sending the cookie upon subsequent requests, however the cookie is available to the adapter through the user's 'attributes' object. It's only a matter of getting the cookie and adding it to the header upon each adapter invocation.
Related
I am attempting to get a modified sample project working with the HeaderLoginModule and HeaderAuthenticator to protect an adapter and then call an adapter function from the sample project by setting the headers and using the WLResourceRequest JavaScript API. I believe that based on my configuration of the loginModule, where I am setting a user-name-header value, and setting this in the header of the WLResourceRequest and then calling send(), that this should provide the user object and then the adapter should be accessible. For some reason though I still get 500 and the log shows 401/unauthorized.
Here are the steps I used to set up this sample:
1) git clone the Cordova sample project at https://github.com/MobileFirst-Platform-Developer-Center/Cordova
2) Added the following sections to authenticationConfig.xml (within the appropriate sections)
<loginModule name="HeaderLoginModule" audit="true">
<className>com.worklight.core.auth.ext.HeaderLoginModule</className>
<parameter name="user-name-header" value="plentyid"/>
<parameter name="display-name-header" value="customername"/>
</loginModule>
<realm name="MyRealm" loginModule="HeaderLoginModule">
<className>com.worklight.core.auth.ext.HeaderAuthenticator</className>
</realm>
<mobileSecurityTest name="MyMobileSecurityTest">
<testUser realm="MyRealm" />
<testDeviceId provisioningType="none" />
</mobileSecurityTest>
3) Secured the adapter with the security test by changing this line in the adapter XML file
<procedure name="getFeed" securityTest="MyMobileSecurityTest"/>
4) Changed the getRSSFeed function as follows
getRSSFeed: function(){
var resourceRequest = new WLResourceRequest(
"/adapters/RSSAdapter/getFeed",
WLResourceRequest.GET);
resourceRequest.addHeader("plentyid","1234");
resourceRequest.addHeader("customername","John Smith");
resourceRequest.setHeader("plentyid","1234");
resourceRequest.setHeader("customername","John Smith");
WL.Logger.info(resourceRequest.getHeaders());
resourceRequest.send().then(app.getRSSFeedSuccess,app.getRSSFeedError);
}
** I will say on the above I could not tell whether to addHeader or setHeader from the documentation. I tried both separately, then both together. They seem to be set from looking at the call to getHeaders().
Thanks for any help with figuring out why this is still 401/Unauthorized when clicking the Adapter button in the app after I mfp push both the RSSAdapter project (MFP) and the Cordova project (app).
We have MobileFirst adapter with a wl_unprotected security test to use it from backend process.
We applied following solution to protect it calling through normal URL
Security Team Restricted URL to be invoked from Outside client application.
Is there any better solution which could be applied to secure this adapter?
There is a very good article in the IBM MobileFirst Platform Developers Center Blog about how to do just that. Protecting adapter procedures for backend access https://developer.ibm.com/mobilefirstplatform/2015/02/04/protect-adapter-backend/
Please go to the article for more details, but here is a summary of the article.
You could use Basic HTTP Authentication to protect that adapter. Update your authenticationConfig.xml file with the securityTest, realm and loginModule as shown below:
authenticationConfig.xml
<securityTests>
<!-- your other security tests -->
<customSecurityTest name="BackendAccessSecurity">
<test realm="BackendAccessRealm" isInternalUserID="true"/>
</customSecurityTest>
</securityTests>
<realms>
<!-- your other realms -->
<realm name="BackendAccessRealm" loginModule="BackendAccessLogin">
<className>com.worklight.core.auth.ext.BasicAuthenticator</className>
<parameter name="basic-realm-name" value="Private"/>
</realm>
</realms>
<loginModules>
<!-- your other login modules -->
<loginModule name="BackendAccessLogin">
<className>com.sample.auth.ConfiguredIdentityLoginModule</className>
<parameter name="username-property" value="backend.username"/>
<parameter name="password-property" value="backend.password"/>
</loginModule>
</loginModules>
worklight.properties
##
# Backend access credentials
##
backend.username=user
backend.password=password
ConfiguredIdentityLoginModule.java
#Override
public void init(Map<String, String> options) throws MissingConfigurationOptionException {
String usernameProperty = options.remove(USERNAME_PROPERTY_CONF);
if (usernameProperty == null) throw new MissingConfigurationOptionException(USERNAME_PROPERTY_CONF);
String passwordProperty = options.remove(PASSWORD_PROPERTY_CONF);
if (passwordProperty == null) throw new MissingConfigurationOptionException(PASSWORD_PROPERTY_CONF);
super.init(options);
WorklightConfiguration conf = WorklightConfiguration.getInstance();
configuredUsername = conf.getStringProperty(usernameProperty);
configuredPassword = conf.getStringProperty(passwordProperty);
if (configuredUsername == null || configuredUsername.length() == 0) {
throw new IllegalStateException("ConfiguredIdentityLoginModule cannot resolve property " + usernameProperty + ". Please check project configuration properties.");
}
if (configuredPassword == null || configuredPassword.length() == 0) {
throw new IllegalStateException("ConfiguredIdentityLoginModule cannot resolve property " + usernameProperty + ". Please check project configuration properties.");
}
}
#Override
public boolean login(Map<String, Object> authenticationData) {
populateCache(authenticationData);
return configuredUsername.equals(username) && configuredPassword.equals(password);
}
Finally, protect your adapter with the BackendAccessSecurity.
We are trying to develop session management on IBMWorklight server. Following are our needs from session management system-
Session should be created once every user logs in using his credentials (username/password).
This session should be able to store session data of that particular user i.e. say session should save his credentials so that he need not to pass those again for accessing other services.
Session timeout should happen after certain time.
Our progress
Created a realm in authenticationConfig:
<realm name="SimpleAuthRealm" loginModule="SimpleAuthLoginModule">
<className>com.worklight.integration.auth.AdapterAuthenticator</className>
<parameter name="login-function" value="SimpleAuthAdapter.onAuthRequired" />
<parameter name="logout-function" value="SimpleAuthAdapter.onLogout" />
</realm>
Created Login module in authenticationConfig:
<loginModule name="SimpleAuthLoginModule">
<className>com.worklight.core.auth.ext.NonValidatingLoginModule</className>
</loginModule>
Created security test:
<customSecurityTest name="SimpleAuthAdapterTest">
<test realm="SimpleAuthRealm" isInternalUserID="true"/>
</customSecurityTest>
We have created a adapter with two procedure in it. Following is adapter.xml file
<procedure name="requestForData" securityTest="SimpleAuthAdapterTest" />
<procedure name="requestForOtherData" securityTest="SimpleAuthAdapterTest" />
Following is adapter implementation file:
function onAuthRequired(headers, errorMessage) {
WL.Logger.info("onAuthRequired(headers, errorMessage)----> START");
WL.Logger.info("headers: " + JSON.stringify(headers));
WL.Logger.info("errorMessage: " + errorMessage);
errorMessage = errorMessage ? errorMessage : null;
WL.Logger.info("onAuthRequired(headers, errorMessage)----> STOP");
return {
authRequired : true,
errorMessage : errorMessage
};
}
function submitAuthentication(username, password) {
WL.Logger.info("submitAuthentication(username, password)----> START");
WL.Logger.info("username: " + username);
WL.Logger.info("password: " + password);
if (username === "worklight" && password === "worklight") {
WL.Logger.info("Login successfull");
var userIdentity = {
userId : username,
displayName : username,
};
WL.Server.setActiveUser("SimpleAuthRealm", userIdentity);
var response = {
authRequired : false,
errorMessage : ""
};
WL.Logger.info("submitAuthentication(username, password)----> STOP");
WL.Logger.info("response: " + JSON.stringify(response));
return response;
}
var response = {
authRequired : true,
errorMessage : "Invalid login credentials"
};
WL.Logger.info("submitAuthentication(username, password)----> STOP");
WL.Logger.info("response: " + JSON.stringify(response));
return response;
}
function onLogout() {
WL.Logger.info("onLogout()---->START");
WL.Server.setActiveUser("SimpleAuthRealm", null);
//WL.Client.logout('SimpleAuthRealm');
WL.Logger.info("onLogout()---->STOP");
}
We have created dummy UI which includes login page and home page. Login button click will call submitAuthentication() service and migrates to home page. Home page consist of two buttons one to call requestForData() service and other for requestForOtherData() service.
Issues we are facing:
This flow demands first to call protected service e.g. requestForData and in response worklight server will throw challenge which we will clear by providing user's credential. We require other way round, we want to provide the user's credentials and start the session so that all the services protected by that realm(security test) should be accessible.
Once we clear challenge for first service we are able to call other service without providing user credentials, but while calling next service we are not passing any identification of calling client, this makes us believe that the session which is established in first service call challenge is for all/ any the user not not user specific. We need very very user specific session.
Please comment on if this is a good idea to maintain session on worklight middleware server as we are working on banking mobile application. Please suggest solutions on above...
For #1, consider setting a security test on all the app environments (in the application descriptor) and Calling WL.Client.connect when the app starts (you should be doing this anyway) This will trigger authentication when the app initially contacts the Worklight server. Once this is complete, you will be able to access adapters protected by security tests in the same realm without additional challenge.
For #2 when you establish the connection to the Worklight server, you create a session that the server tracks as yours, so even though you are not providing credentials again, the Worklight server knows which authenticated user makes each adapter call.
I've been trying to figure this out for a while now, reading a lot of blogs, MSDN documentation, sample code and other stackoverflow questions and have yet to get this to work.
Here is my scenario:
I am using Windows Azure to host two web roles. One is my MVC4 web API, the other is my MVC4 web app which uses the web API. I also have a number of client applications using .NET that will access the web API.
So my main components are:
Web API
Web App
.NET Client
I want to use forms authentication that is 'hosted' in the Web App. I am using the built in simplemembership authentication mechanism and it works great. I can create and log in to accounts in the Web App.
Now I also want to use these same accounts to authenticate the Web API, both from the Web App and any .NET client apps.
I've read numerous ways to do this, the simplest appearing to be using Basic Authentication on the Web API. Currently I am working with this code as it appears to solve my exact problem: Mixing Forms Authentication, Basic Authentication, and SimpleMembership
I can't get this to work. I log in successfully to my Web App (127.0.0.1:81) and when I try to call a Web API that requires authentication (127.0.0.1:8081/api/values for example) the call fails with a 401 (Unauthorized) response. In stepping through the code, WebSecurity.IsAuthenticated returns false. WebSecurity.Initialized returns true.
I've implemented this code and am trying to call my Web API from my Web App (after logging in) with the following code:
using ( var handler = new HttpClientHandler() )
{
var cookie = FormsAuthentication.GetAuthCookie( User.Identity.Name, false );
handler.CookieContainer.Add( new Cookie( cookie.Name, cookie.Value, cookie.Path, cookie.Domain ) );
using ( var client = new HttpClient() )
{
//client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
// "Basic",
// Convert.ToBase64String( System.Text.ASCIIEncoding.ASCII.GetBytes(
// string.Format( "{0}:{1}", User.Identity.Name, "123456" ) ) ) );
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Cookie",
Convert.ToBase64String( System.Text.ASCIIEncoding.ASCII.GetBytes( User.Identity.Name ) ) );
string response = await client.GetStringAsync( "http://127.0.0.1:8080/api/values" );
ViewBag.Values = response;
}
}
As you can see, I've tried both using the cookie as well as the username/password. Obviously I want to use the cookie, but at this point if anything works it will be a good step!
My ValuesController in my Web API is properly decorated:
// GET api/values
[BasicAuthorize]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
In my Global.asax.cs in my Web API, I am initializing SimpleMembership:
// initialize our SimpleMembership connection
try
{
WebSecurity.InitializeDatabaseConnection( "AzureConnection", "User", "Id", "Email", autoCreateTables: false );
}
catch ( Exception ex )
{
throw new InvalidOperationException( "The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex );
}
This succeeds and WebSecurity later says that it is initialized so I guess this part is all working properly.
My config files have matching authentication settings as required per MSDN.
Here is the API config:
<authentication mode="Forms">
<forms protection="All" path="/" domain="127.0.0.1" enableCrossAppRedirects="true" timeout="2880" />
</authentication>
<machineKey decryption="AES" decryptionKey="***" validation="SHA1" validationKey="***" />
Here is the Web App config:
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" protection="All" path="/" domain="127.0.0.1" enableCrossAppRedirects="true" timeout="2880" />
</authentication>
<machineKey decryption="AES" decryptionKey="***" validation="SHA1" validationKey="***" />
Note, I am trying this locally (hence the 127.0.0.1 domain), but referencing a database hosted on Azure.
I haven't got to trying any of this from a .NET client application since I can't even get it working between web roles. For the client app, ideally I would make a web call, passing in username/password, retrieve the cookie, and then use the cookie for further web API requests.
I'd like to get what I have working as it seems pretty simple and meets my requirements.
I have not yet tried other solutions such as Thinktecture as it has way more features than I need and it doesn't seem necessary.
What am I missing?
Well, this is embarrassing. My main problem was a simple code error. Here is the correct code. Tell me you can spot the difference from the code in my question.
using ( var handler = new HttpClientHandler() )
{
var cookie = FormsAuthentication.GetAuthCookie( User.Identity.Name, false );
handler.CookieContainer.Add( new Cookie( cookie.Name, cookie.Value, cookie.Path, cookie.Domain ) );
using ( var client = new HttpClient( handler ) )
...
}
Once that was fixed, I started getting 403 Forbidden errors. So I tracked that down and made a small change to the BasicAuthorizeAttribute class to properly support the [BasicAuthorize] attribute when no role is specified.
Here is the modified code:
private bool isAuthorized( string username )
{
// if there are no roles, we're good!
if ( this.Roles == "" )
return true;
bool authorized = false;
var roles = (SimpleRoleProvider)System.Web.Security.Roles.Provider;
authorized = roles.IsUserInRole( username, this.Roles );
return authorized;
}
With that change basic authentication by passing in the forms cookie works!
Now to get non-web client apps working and then refactor the Web App as recommended.
I hope this helps someone in the future!
We are developing a DataPower(DP) + Worklight(WL) POC
Having this objective in mind, we are following this article: http://www.ibm.com/developerworks/websphere/techjournal/1301_efremenko/1301_efremenko.html
We are clear and on sync with about the DP role on this approach, but we have one question related to the WL code implementation.
At the WL application client code we are using WL HTTP Adapters for all the http requests (REST+JSON) to the backend, like this:
WL.Client.invokeProcedure(invocationData, options);
These adapters are pointing to the DP MPGW endpoint, but based on our understanding, the HTTP Adapter code runs on WL Server.
If it is correct, our assumption for the execution sequence is:
WL Client App -> WL Server -> DP MPGW -> WL Server
When we are looking the same sequence mentioned in the DW article:
WL Client App ->DP MPGW -> WL Server
Could anyone please clarify our understanding about how the WL HTTP Adapter works in this case?
When you're using Worklight Adapter to call DP MPGW, the sequence as below
Request:
WL Client App --> WL Server (Adapter) --> DP MPGW
Response:
DP MPGW --> WL Server (Adapter) --> WL Client App
NOTE: The session id before/after WL server (Adapter) is not the same. If you want to do SSO, you need to pass your LTPA token in adapter to the backend DP. Here's the sample code for you.
Step1. Get LTPA token method (in you ChallengeHandler.js file)
sampleAppRealmChallengeHandler.isCustomResponse = function(response) {
if (!response || response.responseText === null) {
return false;
}
var indicatorIdx = response.responseText.search('j_security_check');
if (indicatorIdx >= 0){
return true;
}else if(response && (response.responseJSON) && (response.responseJSON['WL-Authentication-Success']) && (response.responseJSON['WL-Authentication-Success']['WASLTPARealm'])){
// set ltpaToken when login success
var realm = response.responseJSON['WL-Authentication-Success']['WASLTPARealm'];
ltpaToken = realm.attributes.LtpaToken;
console.log('Get ltpa token success: '+ltpaToken);
}
return false;
};
Step2. Call procedure method (in client App js file)
// define global LTPA token variable
var ltpaToken = null;
function getAccountInfo(){
// check ltpa token is not null, or get the ltap token from login user in WASLTPARealm
if(!ltpaToken){
if(WL.Client.isUserAuthenticated('WASLTPARealm')){
var attrs = WL.Client.getUserInfo('WASLTPARealm', 'attributes');
if(attrs){
ltpaToken = attrs.LtpaToken;
console.log('Set ltpaToken again: '+ltpaToken);
}
}
}
// Pass LTPA token from client App to WL server(adapter)
var token = {'LtpaToken2' : ltpaToken};
var invocationData = {
adapter: "DummyAdapter",
procedure: "getAccountInfo",
parameters: [token]
};
WL.Client.invokeProcedure(invocationData, {
onSuccess: getSecretData_Callback,
onFailure: getSecretData_Callback
});
}
Step3. Pass LTPA token to backend DP in adapter
function getServices( token) {
path = getPath("path/to/services");
var input = {
method : 'post',
returnedContentType : 'json',
path : path,
cookies: token
};
return WL.Server.invokeHttp(input);
}
Developer Works [DW] article correctly says the call sequence. It should be [assuming your mobile application is on customers mobile phone and he/she is operating on internet]
Worklight Mobile Client -> Data Power -> Worklight Server
The reason for this is, datapower acts as a ESB layer providing gateway for all the enterprise services. In a typical environment your Worklight server will be inside intranet and datapower will be on DMZ zone. Now Datapower needs to provide a gateway to worklight service [in your case what you call as adapter]. So the client code on mobile handset is even not aware about any worklight server. It calls the datapower proxy service which in turn scrutinize the request and if valid pass it to backend worklight server. When a response comes back it is also examined and forwarded to client application.
Exactly hosting this service on datapower is relatively easy but making it working requires a lot of effort. LTPA token plays a key role over here in validating client.
Hope this answers your question.
- Ajitabh