Nimbus JOSE JWT expected audience claim to be any of a multiple - kotlin

When validating a JWT I have the scenario that I have a list of allowed client-ids. I put the client-id as audience claim into the JWT but then when verifying I need need to compare against the list.
I tried the following:
val allowedClients = listof("client1", "client2")
val validClaims= JWTClaimsSet.Builder()
.issuer("myIssuer")
.audience(allowedClients)
.build()
val jwtProcessor: ConfigurableJWTProcessor<SecurityContext> = DefaultJWTProcessor()
jwtProcessor.jwsKeySelector = keySelector
jwtProcessor.jwtClaimsSetVerifier = DefaultJWTClaimsVerifier(
//exact match claims
validClaims,
//Required claims
HashSet(listOf("exp", "iss")))
jwtProcessor.jwsKeySelector = keySelector
But if now a JWT is issued with only one client-id which I was doing then the verification fails with
com.nimbusds.jwt.proc.BadJWTException: JWT aud claim has value [client2], must be [client1, client2]
What I can I do such that the Verifier expect any of the client-ids to be in the audience claim but not the full list? Of course an alternative would to add a client-ids to the audience claim to the beginning but I would like to avoid this.

You can define the accepted audience in the verifier. (doc is from nimbus-jose-jwt v9.20)
/**
* Creates new default JWT claims verifier. The expiration ("exp") and
* not-before ("nbf") claims will be checked only if they are present
* and parsed successfully; add them to the required claims if they are
* mandatory.
*
* #param acceptedAudience The accepted JWT audience values,
* {#code null} if not specified. A
* {#code null} value in the set allows JWTs
* with no audience.
* #param exactMatchClaims The JWT claims that must match exactly,
* {#code null} if none.
* #param requiredClaims The names of the JWT claims that must be
* present, empty set or {#code null} if none.
* #param prohibitedClaims The names of the JWT claims that must not be
* present, empty set or {#code null} if none.
*/
public DefaultJWTClaimsVerifier(final Set<String> acceptedAudience,
final JWTClaimsSet exactMatchClaims,
final Set<String> requiredClaims,
final Set<String> prohibitedClaims) {

Related

pass parameters to after-feature karate

I discovered after-feature in karate which is very useful. But I didn't find how to pass parameters to after-feature from main feature. Ex: access token to delete a user account or a user_id.
Here is call of after-feature.feature in my main feature:
* configure afterFeature = function(){ karate.call('classpath: AfterFeature.feature'); }
Here is my AfterFeature.feature
Scenario:
* url 'XXX'
* path 'YYY'
* param foo = bar which should come from main feature
* header Authorization = 'Bearer ' + accessToken which should come from main feature
* method delete
* status 204
karate.call() can take parameters.
karate.call('classpath: AfterFeature.feature', { some: 'value' });

Keycloak migrating hashed passwords

I'm trying to migrate users from an old Drupal 6 CMS to Keycloak. I'd like to migrate the users with their old passwords and then assigning an "Update Password" required action to their profile.
However migrating the passwords seems problematic as I can only access them in their hashed form.
The passwords are hashed with an MD5 algorithm using no salt.
I've tried migrating them according to this page:
https://lists.jboss.org/pipermail/keycloak-user/2015-December/004212.html
Here's the JSON I'm sending to the Keycloak REST API:
{
"hashedSaltedValue" : "password-hash",
"algorithm" : "restcomm-md5",
"type" : "password",
}
Here's a list of things I've tried
Included a NULL hash value
Included a 0 hashIteration value
Base64 encoded the hash
Converted the hash to binary and then Base64 encoding it
Has anyone ever had any luck getting this feature working?
The following curl command worked for me to migrate a old hashed password. Replace {hashedSaltedValue} with your hashed password and {salt} with you salt.
token="..."
curl 'http://keycloak-http/auth/admin/realms/testrealm/users/f:60f0ff50-2cc5-492d-8222-04ac0a9964e1:217b93e8-2830-4392-83e3-9feceea94575' \
-X PUT \
-H "Authorization: $token" \
-H "Content-Type: application/json" \
--data '{"credentials": [ { "algorithm": "pbkdf2-sha512", "hashedSaltedValue": "{hashedpassword}", "hashIterations": 30000, "type": "password", "salt":"{salt}"}]}'
The parameters hashedSaltedValue etc. are deprecated and keycloak 10 and newer will log a deprecation warning.
There is a new CredentialRepresentation defined where you put JSON into the strings for attributes secretData and credentialData.
I'm so late, but my answer may be useful for someone. I have the same problem, we don't want to notify our users to reset password. We are creating users by Keycloak Admin REST API java client. Our user's password are hashed by MD5 algorithm. By default KK don't support MD5, that's why firstly we import custom MD5 password hash provider. Below piece of code that help us.
#Test
public void createUser() {
UserDTO user = UserDTO.builder()
.email("dake#mail.ru")
.username("dake#mail.ru")
.emailVerified(true)
.build();
String rawPassword = "barcelona";
String md5Password = "dea56e47f1c62c30b83b70eb281a6c39";
UserRepresentation userRepresentation = convertToUserRepresentation(user);
//setUserRepresentationPassword(userRepresentation, rawPassword, true);
setUserRepresentationPassword(userRepresentation, md5Password, false);
createUser(userRepresentation);
}
public static UserRepresentation convertToUserRepresentation(UserDTO userDTO) {
UserRepresentation userRepresentation = new UserRepresentation();
userRepresentation.setId(userDTO.getId());
userRepresentation.setEnabled(true);
userRepresentation.setUsername(userDTO.getUsername());
userRepresentation.setFirstName(userDTO.getFirstName());
userRepresentation.setLastName(userDTO.getLastName());
userRepresentation.setEmail(userDTO.getEmail());
userRepresentation.setEmailVerified(userDTO.isEmailVerified());
userRepresentation.singleAttribute("cityId", userDTO.getCityId() != null ? "" + userDTO.getCityId() : null);
userRepresentation.singleAttribute("phone", userDTO.getPhone());
userRepresentation.singleAttribute("phoneVerified", "" + userDTO.isPhoneVerified());
userRepresentation.singleAttribute("notificationsEnabled", "" + userDTO.isNotificationsEnabled());
return userRepresentation;
}
/**
* #return User uuid
*/
public String createUser(UserRepresentation userRepresentation) {
if (CollectionUtils.isEmpty(userRepresentation.getGroups())) {
userRepresentation.setGroups(Arrays.asList(GROUP_USERS));
}
RealmResource realm = keycloak.realm(realmName);
Response response = realm.users().create(userRepresentation);
if (response.getStatus() < 200 || response.getStatus() > 299) {
String error = "User create error: " + response.readEntity(String.class);
log.error(error);
throw new RuntimeException(error);
}
// Extract the uuid of the user we just created.
String location = response.getMetadata().get("Location").get(0).toString();
String uuid = location.substring(location.lastIndexOf("/") + 1);
log.info("User created: " + uuid);
return uuid;
}
/**
* Set password for user
*
* #param userRepresentation user
* #param password raw(plaintext) password or hashed password(this way is deprecated)
* #param isRawPassword password is plaintext
*/
#SneakyThrows
public static void setUserRepresentationPassword(UserRepresentation userRepresentation, String password, boolean isRawPassword) {
CredentialRepresentation credential = new CredentialRepresentation();
credential.setType(CredentialRepresentation.PASSWORD);
credential.setTemporary(false);
if (isRawPassword) {
credential.setValue(password);
} else {
Field algorithm = credential.getClass().getDeclaredField("algorithm");
algorithm.setAccessible(true);
algorithm.set(credential, "MD5");
Field hashIterations = credential.getClass().getDeclaredField("hashIterations");
hashIterations.setAccessible(true);
hashIterations.set(credential, 0);
Field hashedSaltedValue = credential.getClass().getDeclaredField("hashedSaltedValue");
hashedSaltedValue.setAccessible(true);
hashedSaltedValue.set(credential, password);
}
userRepresentation.setCredentials(Arrays.asList(credential));
}
After that everything is good. I noticed, after I logged in my MD5 password are automatically converted to pbkdf2-sha256.
Keycloak reset-password api is, what you're trying to use?
Using "reset-password" api, I believe it only accepts plain text password, which means, you can't reset-password with already hashed password value.
If you use create user api, then you can add hashed value as password.
I am using Aerobase with Keycloak and try to update password using reset-password api, it's not working with hashed password, it only works with plain text password and then store hashed password instead.
If there's anyone who's successfully reset-password with hashed password, please leave comment here!

How to set headers for forwarded request

My controller's methods require a header to be set, e.g. X-Authorization. After a new object has been created (store action), I do a forward to show the newly created object (show action):
$request = Request::create(route('api.v1.b.show', ['booking' => 4]), 'GET');
Request::replace($request->input());
return Route::dispatch($request);
The forwarding works ok if I disable the authorization check, but it fails otherwise. ie. the header has gone. I would like to copy the request header, which I can get with Request::header('X-Authorization') into the forwarded request. Is it possible?
I have tried without success to do $request->header('X-Authorization', 'xxxxx'). Also tried PHP's header() before the dispatch and didn't work.
Any ideas? Cheers
Ok, i think you need to set the headers like so:
$request = Request::create(route('api.v1.b.show', ['booking' => 4]), 'GET');
$request->headers->set('X-Authorization', 'xxxxx');
That is the answer to your question.
My question is: Where can we set this headers for every api request(forwarding)? Because i personally have 5 headers to set with the request and i don't want to repeat myself.
In case anyone might need this i just wanted to post it could help someone
$request =new Request();
$request->headers->set('Authorization', {{your_key_here}});
// From #musicvicious answer
$request = Request::create(route('api.v1.b.show', ['booking' => 4]), 'GET');
If you want to set multiple headers at a time, you can pass more arguments to Request::create()
/**
* Creates a Request based on a given URI and configuration.
*
* The information contained in the URI always take precedence
* over the other information (server and parameters).
*
* #param string $uri The URI
* #param string $method The HTTP method
* #param array $parameters The query (GET) or request (POST) parameters
* #param array $cookies The request cookies ($_COOKIE)
* #param array $files The request files ($_FILES)
* #param array $server The server parameters ($_SERVER)
* #param string $content The raw body data
*
* #return static
*/
Pass your headers starting with 'HTTP_' in $server argument.
$server = [
'HTTP_YOUR_HEADER_NAME_1' => 'Value 1',
'HTTP_YOUR_HEADER_NAME_2' => 'Value 2',
];
Your request will have header like below
your-header-name-1: Value 1;
your-header-name-2: Value 2;

How to use WL.SecurityUtils?

I would like to save cipher binary files in iPhone and to use WL.WL.SecurityUtils of MFP V7.0 JavaScript Client-side.
Could you let me know how to use that?
I have questions as follows.
Q1. Can I set the valeu like a sample 'salt: Math.random().toString()' to Salt(WL.SecurityUtils.keygen parameter) ?
Q2. Could you let me know the return parameter stracture, names and others ?
What I needed to use at WL.SecurityUtils.decrypt?
WL.SecurityUtils.encrypt Returns:
{Promise} Resolved when the operation succeeds, first parameter is an object which includes the cipher text.
Q3.What value can I set the parameter({string} options.iv - Required. Initialization Vector) of WL.SecurityUtils.decrypt ?
When I use iPhone, can I set 'obj' to the parameter {string} options.src - Required. Source ('obj' = iOS, 'java' = Android, 'js' = Web).
WL.SecurityUtils.decrypt Parameters:<br/>
{object} options - Required. <br/>
{string} options.key - Required. Key. <br/>
{string} options.ct - Required. Cipher Text. <br/>
{string} options.iv - Required. Initialization Vector. <br/>
{string} options.src - Required. Source ('obj' = iOS, 'java' = Android, 'js' = Web). <br/>
{number} options.v - Required. Version. <br/>
Q4. Is it OK that I store key and parameters of WL.SecurityUtils.decrypt ( iv, src and v ) to JSONStore ?
JSONStore security utilities
[http://www-01.ibm.com/support/knowledgecenter/SSHS8R_7.0.0/com.ibm.worklight.dev.doc/devref/c_jsonstore_sec_utils_overview.html?lang=en]
Q1. Can I set the value like a sample salt: Math.random().toString() to Salt(WL.SecurityUtils.keygen parameter)
A1. I don't really understand your question here but if you asking how to use WL.SecurityUtils.keygen you can do the following.
WL.SecurityUtils.keygen({
password: 'hello world',
salt : '12345',
iteration : 300
})
Q2. Could you let me know the return parameter stracture, names and others ? What I needed to use at WL.SecurityUtils.decrypt?
A2. Assuming that you are using decrypt in conjunction with encrypt.
var key = 'this is a secret key';
var src = 'js';
WL.SecurityUtils.encrypt({
key : key,
text : 'secret text'
})
.then(function (res){
return WL.SecurityUtils.decrypt({
key : key,
ct : res.ct,
iv : res.iv,
src : src,
v : res.v,
});
});
.then(function (res) {
console.log("Decrypted Text " + res);
})
.fail(function (err) {
console.log("There was a problem : " + JSON.stringify(err));
});
Q3. What value can I set the parameter({string} options.iv - Required. Initialization Vector) of WL.SecurityUtils.decrypt ? When I use iPhone, can I set 'obj' to the parameter {string} options.src - Required. Source ('obj' = iOS, 'java' = Android, 'js' = Web).
A3. The value for options.iv is generated as a response from encrypt.So it is recommended to use both encrypt and decrypt. Src can have the following values : 'java', 'objc', and 'js'. Where java is for Android, objc is for iOS and js is for Web.
Q4. Is it OK that I store key and parameters of WL.SecurityUtils.decrypt ( iv, src and v ) to JSONStore ?
A4. I would say this would be permitted only if your JSONStore is encrypted because you are storing all of your keys for your encrypted resource.
I hope this answers your questions.

Why do I get HTTP 401 Unauthorized from my call the to Yahoo contacts API?

This is driving me crackers. I'm implementing a friend invite scheme on a website and need access to the user's Yahoo contacts list. To do this, I'm using OAuth and the yahoo REST api. Here's a complete rundown of the sequence of events:
I have a project set up on developers.yahoo.com which is configured to have read access to Contacts. It's on a made-up domain which I point to 127.0.0.1 in my hosts file (On the off-chance that localhost was causing my woes). For this reason, the domain is not verified though my understanding is that this simply means I have less restrictions, not more.
Firstly, on the server I get a request token:
https://api.login.yahoo.com/oauth/v2/get_request_token
?oauth_callback=http%3A%2F%2Fdev.mysite.com%2Fcallback.aspx
&oauth_consumer_key=MYCONSUMERKEY--
&oauth_nonce=xmaf8ol87uxwkxij
&oauth_signature=WyWWIsjN1ANeiRpZxa73XBqZ2tQ%3D
&oauth_signature_method=HMAC-SHA1
&oauth_timestamp=1328796736
&oauth_version=1.0
Which returns with (Formatted for vague attempt at clarity):
oauth_token=hxcsqgj
&oauth_token_secret=18d01302348049830942830942630be6bee5
&oauth_expires_in=3600
&xoauth_request_auth_url
=https%3A%2F%2Fapi.login.yahoo.com%2Foauth%2Fv2%2Frequest_auth
%3Foauth_token%3Dhxcsqgj
&oauth_callback_confirmed=true"
I then pop-up the xoauth_request_auth_url page to the user and receive a verifier code to my callback page. I then send that back to my server so that I can exchange it for an access token:
https://api.login.yahoo.com/oauth/v2/get_token
?oauth_consumer_key=MYCONSUMERKEY--
&oauth_nonce=yxhd1nymwd03x189
&oauth_signature=c%2F6GTcybGJSQi4TOpvueLUO%2Fgrs%3D
&oauth_signature_method=HMAC-SHA1
&oauth_timestamp=1328796878
&oauth_token=hxcqgjs
&oauth_verifier=b8ngvp <- verifier given via callback
&oauth_version=1.0
That seems to work, and I get an access token back:
oauth_token=MYVERYLONGACCESSTOKEN--
&oauth_token_secret=MYOATHTOKENSECRET
&oauth_expires_in=3600
&oauth_session_handle=ADuXM093mTB4bgJPKby2lWeKvzrabvCrmjuAfrmA6mh5lEZUIin6
&oauth_authorization_expires_in=818686769
&xoauth_yahoo_guid=MYYAHOOGUID
I then immediately attempt to get the contacts list with the access token and the GUID:
http://social.yahooapis.com/v1/user/MYYAHOOGUID/contacts
(HTTP Header added and formatted with line breaks for clarity...)
Authorization: OAuth
realm="yahooapis.com",
oauth_consumer_key="MYCONSUMERKEY--",
oauth_nonce="nzffzj5v82mgf4mx",
oauth_signature="moVJywesuGaPN5YHYKqra4T2ips%3D",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1328796907",
oauth_token="MYVERYLONGACCESSTOKEN--",
oauth_version="1.0"
From this call I get a 401 Unauthorized, but it seems impossible to find out why. To sign these calls, I'm using this oath lib on github. I don't think it's doing anything extraordinary or incompatable. For the signature, I'm including the consumer key/secret and the access token/secret. I've looked at the signature base that's being hashed and it looks to be the same form as the examples visible on yahoo's documentation. I'm guessing that I'm missing something from the parameters that isn't being hashed. Is there a way to find out why the call is unauthorized, or does anyone know of an example showing exactly what form the signature base and authorization header must take?
Solved this myself. Adding the answer just in case it happens to help anyone who makes the same silly mistake I did. When I made the API call, I was using the token secret returned from the original request token call instead of the new one returned from the access token call.
Oops.
this is the code with which I solved, the trusted code to use if yahooapis returns 403 forbidden:
Reference:
https://developer.yahoo.com/yql/guide/yql-code-examples.html#yql_php
https://github.com/danzisi/YQLQueryYahooapis
init CODE
/**
* Call the Yahoo Contact API
*
* https://developer.yahoo.com/yql/guide/yql-code-examples.html#yql_php
*
* #param string $consumer_key obtained when you registered your app
* #param string $consumer_secret obtained when you registered your app
* #param string $guid obtained from getacctok
* #param string $access_token obtained from getacctok
* #param string $access_token_secret obtained from getacctok
* #param bool $usePost use HTTP POST instead of GET
* #param bool $passOAuthInHeader pass the OAuth credentials in HTTP header
* #return response string with token or empty array on error
*/
function call_yql($consumer_key, $consumer_secret, $querynum, $access_token, $access_token_secret, $oauth_session_handle, $usePost=false, $passOAuthInHeader = true){
global $godebug;
$response = array();
if ($consumer_key=='' || $consumer_secret=='' || $querynum=='' || $access_token=='' || $access_token_secret=='' || $oauth_session_handle) return array('0' => 'Forbidden');
if ($querynum == 1) {
$url = 'https://query.yahooapis.com/v1/yql';
// Show my profile
$params['q'] = 'select * from social.profile where guid=me';
} elseif ($querynum == 2) {
$url = 'https://query.yahooapis.com/v1/yql';
// here other query
}
$params['format'] = 'json'; //json xml
$params['Authorization'] = 'OAuth';
$params['oauth_session_handle'] = $oauth_session_handle;
$params['realm'] = 'yahooapis.com';
$params['callback'] = 'cbfunc';
$params['oauth_version'] = '1.0';
$params['oauth_nonce'] = mt_rand();
$params['oauth_timestamp'] = time();
$params['oauth_consumer_key'] = $consumer_key;
$params['oauth_callback'] = 'oob';
$params['oauth_token'] = $access_token;
$params['oauth_signature_method'] = 'HMAC-SHA1';
$params['oauth_signature'] = oauth_compute_hmac_sig($usePost? 'POST' : 'GET', $url, $params, $consumer_secret, $access_token_secret);
if ($passOAuthInHeader) {
$query_parameter_string = oauth_http_build_query($params, true);
$header = build_oauth_header($params, "yahooapis.com");
$headers[] = $header;
} else {
$query_parameter_string = oauth_http_build_query($params);
}
// POST or GET the request
if ($usePost) {
$request_url = $url;
logit("call_yql:INFO:request_url:$request_url");
logit("call_yql:INFO:post_body:$query_parameter_string");
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
$response = do_post($request_url, $query_parameter_string, 443, $headers);
} else {
$request_url = $url . ($query_parameter_string ? ('?' . $query_parameter_string) : '' );
logit("call_yql:INFO:request_url:$request_url");
$response = do_get($request_url, 443, $headers);
}
// extract successful response
if (! empty($response)) {
list($info, $header, $body) = $response;
if ($godebug==true) {
echo "<p>Debug: function call_yql info: <pre>" . print_r($info, TRUE) . "</pre></p>";
echo "<p>Debug: function call_yql header: <pre>" . print_r($header, TRUE) . "</pre></p>";
echo "<p>Debug: function call_yql body: <pre>" . print_r($body, TRUE) . "</pre></p>";
}
if ($body) {
$body = GetBetween($body, 'cbfunc(', ')');
$full_array_body = json_decode($body);
logit("call_yql:INFO:response:");
if ($godebug==true) echo "<p>Debug: function call_yql full_array_body: <pre>" . print_r($full_array_body, TRUE) . "</pre></p>";
}
}
// return object
return $full_array_body->query;
}
END code