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.
Related
I have a MVC client accessing a Web API protected by IDS4. They all run on my local machine and hosted by IIS. The app works fine when using local identity for authentication. But when I try to use Windows authentication, I keep getting "401 Unauthorized" error from the dev tool and the login box keeps coming back to the browser.
Here is the Windows Authentication IIS setting
and enabled providers
It's almost like that the user ID or password was wrong, but that's nearly impossible because that's the domain user ID and password I use for logging into the system all the time. Besides, according to my reading, Windows Authentication is supposed to be "automatic", which means I will be authenticated silently without a login box in the first place.
Update
I enabled the IIS request tracing and here is the result from the log:
As you can see from the trace log item #29, the authentication (with the user ID I typed in, "DOM\Jack.Backer") was successful. However, some authorization item (#48) failed after that. And here is the detail of the failed item:
What's interesting is that the ErrorCode says that the operation (whatever it is) completed successfully, but still I received a warning with a HttpStatus=401 and a HttpReason=Unauthorized. Apparently, this is what failed my Windows Authentication. But what is this authorization about and how do I fix it?
In case anyone interested - I finally figured this one out. It is because the code that I downloaded from IndentityServer4's quickstart site in late 2020 doesn't have some of the important pieces needed for Windows authentication. Here is what I had to add to the Challenge function of the ExternalController class
and here is the ProcessWindowsLoginAsync function
private async Task<IActionResult> ProcessWindowsLoginAsync(string returnUrl)
{
var result = await HttpContext.AuthenticateAsync(AccountOptions.WindowsAuthenticationSchemeName);
if (result?.Principal is WindowsPrincipal wp)
{
var props = new AuthenticationProperties()
{
RedirectUri = Url.Action(nameof(Callback)),
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", AccountOptions.WindowsAuthenticationSchemeName },
}
};
var id = new ClaimsIdentity(AccountOptions.WindowsAuthenticationSchemeName);
id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name));
id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name));
if (AccountOptions.IncludeWindowsGroups)
{
var wi = wp.Identity as WindowsIdentity;
var groups = wi.Groups.Translate(typeof(NTAccount));
var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value));
id.AddClaims(roles);
}
await HttpContext.SignInAsync(IdentityConstants.ExternalScheme, new ClaimsPrincipal(id), props);
return Redirect(props.RedirectUri);
}
else
{
return Challenge(AccountOptions.WindowsAuthenticationSchemeName);
}
}
Now my windows authentication works with no issues.
Authentication adapter throwing "Procedure invocation error" sometimes. Tried clearing cache and cookies but still the same. So we tried to login from different system for same user and it works. This is quite confusing as once we try with different ID in browser where issue occurred, it works and then it works with Member ID which has issue as well. Auth required is not coming in response when issue occurs.
we have tried to look into logs and found WorklightAuthenticationException from Authentication Adapter while trying security test procedure.
Authentication Adapter code:
var result = WL.Server.invokeHttp(input);
WL.Logger.info("Authentication service : " + JSON.stringify(result));
authResponse = prepareJSONResponse(result,channelId);
WL.Logger.info('Formatted response -> ' + JSON.stringify(authResponse));
if(result.isSuccessful == false){
WL.Logger.info("Error: " + result.errorMessage);
return onAuthRequired(null, "Error in connecting to server. Please try again later.");
}
if(typeof authResponse.errorMessage != 'undefined'){
WL.Logger.info("Error is defined" +authResponse.errorMessage);
return onAuthRequired(null, authResponse);
}
WL.Logger.info("Authentication service success: " + JSON.stringify(result));
WL.Logger.info("userIdentity Parameters: " + inputParams.CorpId);
var userIdentity = {
userId: inputParams.CorpId,
displayName: inputParams.CorpId,
attributes: {
foo: "bar"
}
};
WL.Logger.info("userIdentity::"+JSON.stringify(userIdentity));
WL.Server.setActiveUser("SingleStepAuthRealm", userIdentity);
return {
authRequired: false
};
It is happening due to the requests going from one node to another node. Handled it in Load balancer to send requests to specific node based on cookies and post that it works fine.
The description mentions about clearing cache and cookies and using browser.
Browser based environments are not supported in session independent mode. These work only in session dependent mode. As such, it is imperative that session based affinity be enabled to ensure the requests land in the same JVM for authentication state to be preserved.
More details can be found here : Session-independent mode
I have self hosted Service Stack server. In client application, it is Windows Form, I want to display login form when user is not authenticated. One way is to try send request and catch WebServiceException like in AuthTest.
I'm looking for #if(Request.IsAuthenticated) (SocialBootstrapApi example) equivalent for client side.
Short answer is there is no way to determine on the client if the User is authenticated or not as the state that determines whether or not a client is Authenticated is stored on the Server.
A user is only Authenticated if there's an Authenticated Session stored in the Users session Id. This SessionId is generally sent in the HTTP Client Cookies which get populated after a successful Authentication attempt, e.g:
var client = JsonServiceClient(BaseUrl);
var authResponse = client.Send(new Authenticate
{
provider = CredentialsAuthProvider.Name,
UserName = "user",
Password = "p#55word",
RememberMe = true,
});
You can now use the same client (which has its Cookies populated) to make Authenticated Requests to Auth Only Services. Although note if the Server clears out the Users session for any reason the client is no longer authenticated and will throw 401 UnAuthorized HTTP Exceptions at which point they will have to re-Authenticate.
The MVC LiveDemo shows examples of Authentication and retrieving Session on the client with ajax to determine whether the user is authenticated or not by calling /auth, e.g:
$.getJSON("/api/auth", function (r) {
var html = "<h4 class='success'>Authenticated!</h4>"
+ "<table>"
+ $.map(r, function(k, v) {
return "<tr><th>" + v + "<th>"
+ "<td>"
+ (typeof k == 'string' ? k : JSON.stringify(k))
+ "</td></tr>";
}).join('')
+ "</table>";
$("#status").html(html);
}).error(function () {
$("#status").html("<h4 class='error'>Not Authenticated</h4>");
The equivalent with C# is:
try
{
var response = client.Get(new Authenticate());
//Authenticated
}
catch (WebServiceException)
{
//Not Authenticated
}
I have done adapter based authentication and there is no problem in authentication and it works fine. I have faced some issues in getting the active users useridentity.The code may explain you a bit more
adapterAuthRealmChallengeHandler.handleChallenge = function(response){
var authRequired = response.responseJSON.authRequired;
if (authRequired == true){
if (response.responseJSON.errorMessage)
alert(response.responseJSON.errorMessage);
} else if (authRequired == false){
adapterAuthRealmChallengeHandler.submitSuccess();
setTimeout(function(){pageTransitionCall();},10000); //this code only works
pageTransitionCall(); //This throws null error in console
}
};
function pageTransitionCall(){
console.log(WL.Client.getUserName("AdapterAuthRealm"));
}
As you can see i was trying to get the active userName of the realm. The WL.Client.getUserName("AdapterAuthRealm") only works after some time interval only and i am not sure about the time interval. By adapter code is as below
function submitAuthentication(username, password,userCred){
if (username==="worklight" && password === "worklight"){
WL.Logger.info("if");
var userIdentity = {
userId: userCred,
displayName: userCred,
attributes: {
foo: "bar"
},
loginName : userCred,
userName : userCred
};
WL.Server.setActiveUser("AdapterAuthRealm", userIdentity);
WL.Logger.info(JSON.stringify(userIdentity));
return {
authRequired: false
};
}
else
{
WL.Logger.info("else");
return onAuthRequired(null, "Invalid login credentials");
}
}
My doubt is why does the client cant retrieve the activeuser. And i am sure that my code is correct and active user is set and i can see in the server log.After the setactvieruser is set only i have return false in the adpter and why cant the client retrieve the user at instant and why it needs delay to retrieve. i have verified in both Worklight V6.0 and also Worklight V6.1.i have created the Ipad environment.
The info that contains logged in userId (basically any userIdentity data) is not returned immediately after adapter authentication but only when an original request succeeds. Consider this
You're making request#1 to the server (let's say invoke procedure)
You're getting response with authRequired:true
You're submitting auth data
You're getting authRequred:false
You're calling submitSuccess()
WL framework automatically re-invokes request#1
You're getting response for request#1
userIdentity data will be returned in step7 and not in step4. Basically once you start authentication flow you're considered out of the original invocation context. You need to finish the flow and tell WL framework that auth has completed. Once you do - WL framework will reinvoke the original request. WL server add userIdentity data to the response and WL client will update userName, displayName etc properties.
In case you need user data before that, e.g. right away once auth is complete, you can add custom properties to your submitAuthentication function response, e.g.
WL.Server.setActiveUser("AdapterAuthRealm", userIdentity);
return {
authRequired: false,
loginName: userIdentity.loginName
};
this will make sure that loginName will be returned to your handleChallenge function. you can retrieve it there and do whatever you want with it.
Any one has experience in development of Login module with ProviderService.Src.
I need to develop a login module with the webservice using Titanium Appcelerator. It needs to take two strings (Username & Password) and return true/false. I need to send entered login and password via web service and receive true/false. The webservice is
http://46.18.8.42/FareBids.Service/FareBids.ServiceLayer.ProviderService.svc
Please some one guide me how to create a Login module with it? I got information which says me to use SUDS. Can some one help me on this with code(if possible)
Help would really be appreciated. Thanks.
Use a webservice http client. This should do it, you will have to tune this to your specific webservice and obviously collect data from the user, but how to do that is well documented in the most basic Titanium tutorials.
var loginSrv = Ti.Network.createHTTPClient({
onload : function(e) {
// If the service returns successfully : true, or false
var isUserAllowed = this.responseText;
},
onerror : function(e) {
// Web service failed for some reason
Ti.API.info(this.responseText);
Ti.API.info('webservice failed with message : ' + e.error);
}
});
loginSrv.open('POST', 'http://46.18.8.42/FareBids.Service/FareBids.ServiceLayer.ProviderService.svc');
// you may have to change the content type depending on your service
loginSrv.setRequestHeader("Content-Type", "application/json");
var sendObj = {loginid : 'someid', pwd : 'somepassword'};
loginSrv.send(obj);