Websphere Role Based WS-Security with UsernameToken - web-services

Through the Websphere Console I've setup a Policy Set and a Policy Set Binding in order to support UsernameToken authentication on a webservice. As expected, it is rejecting web service calls without correct usernames and passwords. However, it is now accepting every user in the connected LDAP.
I would like to be able to only allow access to users in a specific LDAP group. I have the feeling that I need to create a custom JAAS Login in the Caller settings, but I'm not completely sure.
Does anybody have a solution for this, or a direction where I should be looking?
Edit: I'm doing this to expose an IBM BPM web service.

Create your web service based on EJB not a POJO, and then use #RolesAllowed annotation to specify roles which are allowed to invoke particular method from your service. Use adminconsole, scirpt or binding file to map defined role to user or groups from the LDAP server.
This is probably much easier than fighting with Login module and more flexible.

You can create a custom JAAS login module to use when consuming the username token. You can use a JAAS config that first calls the built-in token consumer, then your custom consumer. Doing it this way means that you can use the built-in consumer to parse the token and do timestamp and nonce processing and you only have to do the username/password validation in your own login module.
The instructions can be found here: http://www14.software.ibm.com/webapp/wsbroker/redirect?version=phil&product=was-nd-dist&topic=twbs_replace_authmethod_usernametoken
(Please forgive the formatting. I'm doing the best I can with what I have available here.)
Replacing the authentication method of the UsernameToken consumer using a stacked JAAS login module
By default, the Web services security UsernameToken consumer, UNTConsumeLoginModule, always validates the username and password that are contained within the token against the WebSphere registry. You can use the SPIs that GenericSecurityTokenFactory provides to bypass this authentication method.
About this task
If you want to replace the authentication method that UNTConsumeLoginModule uses, you must provide your own custom JAAS login module to do the authentication. The custom login module is stacked under UNTConsumeLoginModule in a custom JAAS configuration. The UNTConsumeLoginModule consumes and validates the token XML. The validation of the values provided for username and password is deferred to the custom stacked login module.
Because the use of UNTConsumeLoginModule carries with it the assumption that the username and password will be authenticated, more requirements are put on a stacked login module that intends to perform this function than are put on login modules that are only intended to provide dynamic token functionality.
To indicate to UNTConsumeLoginModule that it should not authenticate the username and password, you must set the following property on the configured callback handler:
com.ibm.wsspi.wssecurity.token.UsernameToken.authDeferred=true
Like most WS-Security login modles, UNTConsumeLoginModule always puts the consumed token in the shared state map to which all login modules in the stack have access. When authDeferred=true is specified, in the commit phase, UNTConsumeLoginModule ensures that the same UsernameToken object that had originally been put on the shared state has been put in another location in the shared state. If this UsernameToken object cannot be found, a LoginException occurs. Therefore, you cannot just set authDeferred=true on the callback handler without having an accompanying login module return the token to the shared state.
Procedure
Develop a JAAS login module to do the authentication and make it available to your application code. This new login module stacks under the com.ibm.ws.wssecurity.wssapi.token.impl.UNTConsumeLoginModule.
This login module must:
Use the following method to get the UsernameToken that UNTConsumeLoginModule consumes.
UsernameToken unt = UsernameToken)factory.getConsumerTokenFromSharedState(sharedState,UsernameToken.ValueType);
In this code example, factory is an instance of com.ibm.websphere.wssecurity.wssapi.token.GenericSecurityTokenFactory.
Check the username and password in the manner that you choose.
You can call unt.getUsername() and unt.getPassword() to get the username and password.
Your login module should throw a LoginException if there is an authentication error.
Put the UsernameToken, that was obtained in the previous substep, back on the shared state.
Use the following method to put the UsernameToken back on the shared state.
factory.putAuthenticatedTokenToSharedState(sharedState, unt);
Following is an example login module:
package test.tokens;
import com.ibm.websphere.wssecurity.wssapi.token.GenericSecurityTokenFactory;
import com.ibm.websphere.wssecurity.wssapi.WSSUtilFactory;
import java.util.HashMap;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import com.ibm.websphere.wssecurity.wssapi.token.UsernameToken;
import java.util.ArrayList;
import com.ibm.wsspi.security.registry.RegistryHelper;
import com.ibm.websphere.security.UserRegistry;
public class MyUntAuthenticator implements LoginModule {
private Map _sharedState;
private Map _options;
private CallbackHandler _handler;
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map<String, ?> sharedState, Map<String, ?> options) {
this._handler = callbackHandler;
this._sharedState = sharedState;
this._options = options;
}
public boolean login() throws LoginException {
//For the sake of readability, this login module does not
//protect against all NPE's
GenericSecurityTokenFactory factory = null;
WSSUtilFactory utilFactory = null;
try {
factory = GenericSecurityTokenFactory.getInstance();
utilFactory = WSSUtilFactory.getInstance();
} catch (Exception e) {
throw new LoginException(e.toString());
}
if (factory == null) {
throw new LoginException("GenericSecurityTokenFactory.getInstance() returned null");
}
UsernameToken unt = (UsernameToken)factory.getConsumerTokenFromSharedState(this._sharedState,UsernameToken.ValueType);
String username = unt.getUsername();
char [] password = unt.getPassword();
//authenticate the username and password
//to validate a PasswordDigest password (fixpack 8.5.5.8 and later)
//String pw = yourCodeToLookUpPasswordForUsername(username);
//boolean match = utilFactory.verifyDigestedPassword(unt, pw.toCharArray());
//if (!match) throw new LoginException("Digested passwords do not match");
//Example:
try {
simpleUserGroupCheck(username, password, "cn=group1,o=ibm,c=us");
} catch (Exception e) {
LoginException le = new LoginException(e.getMessage());
le.initCause(e);
throw le;
}
//Put the authenticated token to the shared state
factory.putAuthenticatedTokenToSharedState(this._sharedState, unt);
return true;
}
private boolean simpleUserGroupCheck(String username, char [] password, String group) throws Exception {
String allowedGroup = null;
//get the default user registry
UserRegistry user_reg = RegistryHelper.getUserRegistry(null);
//authenticate the user against the user registry
user_reg.checkPassword(username, new String(password));
//get the list of groups that the user belongs to
java.util.List<String> groupList = user_reg.getGroupsForUser(username);
//you can either use a hard-coded group
allowedGroup = group;
//or get the value from your own custom property on the callback handler
//WSSUtilFactory util = WSSUtilFactory.getInstance();
//Map map = util.getCallbackHandlerProperties(this._handler);
//allowedGroup = (String) map.get("MY_ALLOWED_GROUP_1");
//check if the user belongs to an allowed group
if (!groupList.contains(allowedGroup)) {
throw new LoginException("user ["+username+"] is not in allowed group ["+allowedGroup+"]");
}
return true;
}
//implement the rest of the methods required by the
//LoginModule interface
}
Create a new JAAS login configuration.
In the administrative console, select Security > Global security.
Under Authentication, select Java Authentication and Authorization Service.
Select System logins.
Click New, and then specify Alias = test.consume.unt.
Click New, and then specify Module class name = com.ibm.ws.wssecurity.wssapi.token.impl.UNTConsumeLoginModule
Click OK.
Click New, and then specify Module class name = test.tokens.MyUntAuthenticator
Select Use login module proxy.
Click OK, and then click SAVE.
Configure your UsernameToken token consumer to use the new JAAS configuration.
Open your bindings configuration that you want to change.
In the administrative console, select WS-Security > Authentication and protection.
Under Authentication tokens, select the UsernameToken inbound token that you want to change.
Select JAAS login = test.consume.unt.
Set the required property on the callback handler that is configured for the UsernameToken consumer.
Click Callback handler.
Add the com.ibm.wsspi.wssecurity.token.UsernameToken.authDeferred=true custom property.
Click OK.
Click SAVE.
Restart the application server to apply the JAAS configuration changes.
Test your service.

Related

WSO2 ApiManager 3.2 - Adaptive Authentication function assignUserRoles is not defined

I am trying to using an openAM external identity provider to authenticate users of the WSO2 developer portal.
To do that I need to dynamically add the role Internal/subscriber to user authenticated via openAM because we don't have roles in openAM.
So I added the following code to the Script Based Adaptive Authentication:
var onLoginRequest = function(context) {
executeStep(1, {
onSuccess: function (context) {
// Extracting authenticated subject from the first step.
var user = context.currentKnownSubject;
assignUserRoles(user, ['Internal/subscriber']);
}
});
};​
Trying the authentication I see in the wso2 logs the error "assignUserRoles" is not defined:
TID: [-1234] [] [2021-06-10 10:57:34,273] ERROR {org.wso2.carbon.identity.application.authentication.framework.config.model.graph.JsGraphBuilder} - Error in executing the javascript for service provider : apim_devportal, Javascript Fragment :
function (context) {
// Extracting authenticated subject from the first step.
var user = context.currentKnownSubject;
assignUserRoles(user, ['Internal/subscriber']);
} <eval>:4 ReferenceError: "assignUserRoles" is not defined
at jdk.nashorn.internal.runtime.ECMAErrors.error(ECMAErrors.java:57)
at jdk.nashorn.internal.runtime.ECMAErrors.referenceError(ECMAErrors.java:319)
at jdk.nashorn.internal.runtime.ECMAErrors.referenceError(ECMAErrors.java:291)
at jdk.nashorn.internal.objects.Global.__noSuchProperty__(Global.java:1442)​
Any idea on how to solve this? Or any other alternative to give a default Internal/subscriber to any user authenticated via OpenAM?
The above-mentioned error is expected in the API Manager servers. This is because the API Manager servers are not built to support Adaptive Authentication as WSO2 IS servers.
So, if you are planning to perform the Adaptive Authentication, the best option would be to deploy a WSO2 IS server as a Key Manager with API Manager and perform the task. Further, as an alternative way, we can implement a custom Provisioning Handler to assign the `Internal/subscriber' role to the provisioning users.
You can refer to the SystemRolesRetainedProvisionHandler.java implementation for more clarity. We can make use of the retrieveRolesToBeDeleted() to append the Internal/subscriber role into the rolesToAdd variable and then configure the custom provisioning handler in the API Manager with the following TOML config
[authentication.framework.extensions]
provisioning_handler = "com.sample.custom.CustomRoleProvisioningHandler"
A sample implementation is given below
// CustomRoleProvisioningHandler.java
import java.util.List;
import java.util.Map;
import org.wso2.carbon.identity.application.authentication.framework.exception.FrameworkException;
import org.wso2.carbon.identity.application.authentication.framework.handler.provisioning.impl.DefaultProvisioningHandler;
import org.wso2.carbon.identity.application.authentication.framework.handler.provisioning.impl.SystemRolesRetainedProvisionHandler;
import org.wso2.carbon.user.core.UserRealm;
import org.wso2.carbon.user.core.UserStoreException;
public class CustomRoleProvisioningHandler extends SystemRolesRetainedProvisionHandler {
#Override
public void handle(List<String> roles, String subject, Map<String, String> attributes,
String provisioningUserStoreId, String tenantDomain) throws FrameworkException {
roles.add("Internal/subscriber");
super.handle(roles, subject, attributes, provisioningUserStoreId, tenantDomain);
}
}
Further here are few other implementations for custom provisioning handlers
tharakawijekoon/custom-provisioning-handler
nipunthathsara/wso2-custom-provisioning-handler
Hope this helps to achieve your requirement.

Customize email verification page AWS Cognito

I am using Amazon Cognito for user authentication. After the user is registered verification email is sent to his email address. After clicking on the email link he is prompted with this in his browser.
How can I customize this page in order to insert a script that will trigger deep links within the mobile application, and also make the page look bit nicer?
You can do that using Cognito triggers.
You can configure a trigger template to define a message with a link to a page you control.
The assets will be stored at: amplify/backend/auth/<your-resource-name>CustomMessage/assets
The documentation has more details
Cognito allows you to configure your User Pool to send an email to
your users when they attempt to register an account. You can configure
this email to contain a link to Cognito’s Hosted UI where the user’s
account will be marked as confirmed.
This trigger template allows you to define an email message with a
link to a static S3 bucket that you control, where the user’s account
will be confirmed and they can then be redirected to a URL of your
choice (presumably your application). The URL will automatically
contain the username as a query string parameters.
Please note that this trigger template will create an S3 resource. The
files that populate the static site are available for edit in
amplify/backend/auth/CustomMessage/assets. They
consist of:
index.html
spinner.js (controls the spinner that appears on the page while users are awaiting confirmation)
style.css
verify.js (the script which performs the verification request)
I was not able to customize the verification page provided by AWS. I created my own UI on my page, which sent the generated code to cognito for verification. For that I needed to:
trigger custom email upon registration
put custom link to verification in the email using the codes provided for the lambda
process the codes on my page
send the codes and username through aws package
Step 1.
In AWS Cognito User Pool, customize workflow with triggers, choose "Custom Message". The triggerSource for verification that I check for are:
event.triggerSource === 'CustomMessage_SignUp' || event.triggerSource === 'CustomMessage_ResendCode'
You can see other trigger sources for CustomMessage here: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-custom-message.html
Step 2. The lambda provides parameters for verification for my users: event.request.userAttributes.sub and event.request.codeParameter. Using these to I constructed a link to my page like so:
https://mypage.com?user_name=${event.request.userAttributes.sub}&confirmation_code=${event.request.codeParameter}
Step 3. On my page, I check if the url params for user_name and confirmation_code are present, and display a modal which is supposed to inform the user if the verification went correctly or not.
Using a package "amazon-cognito-identity-js" I process the code and user_name. First I create the user pool:
import { CognitoUserPool } from 'amazon-cognito-identity-js';
//Aws-cognito credentials
const poolData = {
UserPoolId: YOUR_USERPOOL_ID,
ClientId: YOUR_CLIENT_ID,
};
export default new CognitoUserPool(poolData);
Then to process the code I create a user instance:
import { CognitoUser } from 'amazon-cognito-identity-js';
import UserPool from 'utils/UserPool';
const getUser = () => {
return new CognitoUser({
Username: user_name.toLowerCase(),
Pool: UserPool,
});
};
// After that you can process the code:
getUser().confirmRegistration(code, false, function (err, result) {
if (err) {
if (
err.message === 'User cannot be confirmed. Current status is CONFIRMED'
) {
// Handle already confirmed error
} else {
// Handle other errors you want
}
}
// Handle successful verification
});
The account is verified and you can guide the user to the login page or any other.

Store UserTokens generated by ASP.Net Core identity for external login provider

I am using Facebook as external login of ASP.Net Core identity.
I would like, even if the user logged in with Facebook, the user to fill his profile on the website.
For that I use the ExternalLoginCallback method, from which I would like to get data from Facebook such as date of birth, location (country), ...
One issue is if the user unchecked some of the permissions, the default call to Facebook fails:
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
return RedirectToAction(nameof(Login));
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
I would also need to do some additional checks on user data, which would require calling directly the Graph API.
My blocking points:
- In the ExternalLoginCallback method, I would need to separate the 'country' and 'birthday' to avoid the Facebook API to return an error in case of the user didn't grant the permission
- For that I would need the the user access_token (and for additional calls in the method), I don't see how to get it even if it is used by the Facebook Identity framework
- Once the profile created, I would like to get access to this access_token, which should be stored in the UserTokens table (I guess?), but I can't find it there, the table is empty. (my DbContext is a class extending IdentityDbContext<AppUser, AppRole, long>, don't know if it has an impact)
I have found this answer https://stackoverflow.com/a/42670559/4881677 which may help, but not sufficient.
Any help? :)
In order to store the user Facebook token, it requires to specify it in the options (not stored by default).
var fo = new FacebookOptions();
fo.SaveTokens = true;
From there we can call the graph method permissions to get the available permissions: https://graph.facebook.com/me/permissions?access_token={token}
Then it can be read with something like this:
foreach (var perm in data)
{
perms.Add((string)perm["permission"], (string)perm["status"]);
}

Meteor share sessions data between client and server

I'm building a restricted signup. I want user with a specific code passed in a url to be able to signup and not others. I'm using the accounts package.
I can prevent account creation in the Accounts.onCreateUser method. I'm looking for a way to tell the server if the client had an authorised signup code. With a classic form (email+password) I can just add an extra hidden field. How can I achieve the same result if the user signs up with let's say Facebook?
Since Meteor doesn't use cookies, I can't store this info in a cookie that the server would access. Session variable are not accessible server side. And since I'm not controlling what got send with the account-facebook creation, I can't use a Session variable on the client side that I'd pass along when the user presses sign up.
Any idea"?
Just add the special token to the user object being passed to Accounts.createUser():
var user = {
email: email,
password: password,
profile: {
token: token
}
};
Accounts.createUser(user, function (error, result) {
if (error) {
console.log(error)
}
});
On the server side you can access this in the Accounts.onCreateUser():
Accounts.onCreateUser(function(options, user) {
console.log(options);
console.log(user);
});
I think it's in the options variable that you will find your token, so it would be options.profile.token.
for me, the best option here was passing in custom parameters to loginbuttons.
see the package docs:
https://github.com/ianmartorell/meteor-accounts-ui-bootstrap-3
Where it outlines the below:
accountsUIBootstrap3.setCustomSignupOptions = function() {
return {
mxpDistinctId: Session.get('mxpdid'),
leadSource: Session.get('leadSource')
}
};

Rest Replay Attacks / Security

let me describe a simple scenario:
There is a rest resource represented by this URI: /api/messages/{userid}. After the user is logged in, a request is dispatched passing to this URI "userid" (logged user). This way, as soon as the user logs in, he gets his own messages based on his ID.
If user is not logged yet, the URI is not visible (there is a authentication filter).
The problem is: if a already logged user discover this uri, he can submit a request passing another ID what will lead to a security problem because he will be able to get messages from another user (simply passing any random ID).
Can you propose any security model to prevent this security flaw ? (what I believe its more likely a cross-cutting concern).
Thanks, in advance!
Just off the top of my head, have the endpoint either (a) only return those messages visible by the logged-in user, or (b) only return messages if the logged-in user is the same as the userId in the URI. Which one depends on your business rules.
In the endpoint you can check for the user and then see if that user id matches the pathparam
finding the userid from the user name is as easy as selecting the id using the user name from a database
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;
import ... blablabla;
#GET
#Path("messages/{userid}")
public Response getMessages( #PathParam("userid") int userId, #Context SecurityContext context ) {
String username = context.getUserPrincipal().getName();
int id = getIdFromName(username); // define this yourself
if(userId==id) {
// output the message list
List<Message> msgs = getDemMessagesGURL(); // define this yourself
return Response.ok(new GenericEntity<List<T>>(msgs) {}).build();
} else {
// output Forbidden(403)
return Response.status(403).build();
}
}
and with oauth you can use a servlet filter to set the user principal based on the oauth signiture