Out of the box, grouper-ws uses self-service JWT authentication in v2.6.4+). This is enabled by default
This authentication is built-in to Grouper and does not use tomcat or apache authentication.
If there is a JWT in the request it will use that. Otherwise it will use whatever other authentication mechanism. So this co-exists with other authn methods.
This is public / private key encryption, do not send the private key from client to server. The JWT needs to be generated new for each call
Note: if you have grouper-ws.properties property ws.client.user.group.name configured, then the local entity needs to be added to that configured group to be able to call WS.
Note: the iat must be in the jwt.
Configure
grouper.properties
##################################
## ws self-service jwt
##################################
# if public private key should be enabled
# {valueType: "boolean", defaultValue: "true"}
grouper.selfService.jwt.enable =
# if you fill in a group name here, then only members of this group can manage jwt private keys on the ui
# {valueType: "string"}
grouper.selfService.jwt.groupNameAllowedToManage =
# maximum number of seconds for which a jwt can stay valid
# {valueType: "integer"}
grouper.selfService.jwt.maxValidTimeInSeconds = 600
Register
Anyone in the Grouper UI who can CREATE objects in a folder, can create a local entity. A local entity in Grouper is an object that is like a group with no members and with few privileges (e.g. you cant have UPDATE on something with no members). If someone creates a local entity (or is created privileges on it), then they have ADMIN of the local entity. Someone with ADMIN on a local entity can manage the WS JWT key.
Calling web services with self service JWT authentication
Save the private key and keep this safe. The algorithm used to create the private key is RSA-256 (RS256). It consists of PEM PKCS#1 data without the PEM header/footer. Depending on your JWT implementation method, appropriate header/footer may be required:
String pattern = "yyyy/MM/dd HH:mm:ss.SSS";
java.text.SimpleDateFormat simpleDateFormat = new java.text.SimpleDateFormat(pattern);
java.util.Date date = null;
try {
date = simpleDateFormat.parse("2021/10/23 16:24:08.512");
} catch (java.text.ParseException pe) {
throw new RuntimeException("error", pe);
}
--or--
java.util.Date date2 = new java.util.Date(1635020648512L);
Put that date in the issuedAt
.withIssuedAt(new Date())
Now there is the prefix from the entity page, and the JWT, concatenate that together. Note, you can put an expire date in the JWT, and the Grouper server will only allow JWTs of a certain age (see config above). So make sure clocks are synchronized and that a JWT that is re-used is not expired. Also, you need to grant privileges to the local entity consistent with what access is needed. In this case the local entity was granted READ on the group - test:group1
This is the prefix of all requests for this entity (get from UI when you download the token). The "YWMyOTE.." is the base64 encoding of the local entity member ID (uuid in the grouper_members table). That part generally shouldnt matter, just get from UI.
Email people whose credentials are about to expire
Add a notification daemon with this query to email people about their credential about to expire. This is postgres, could be adjusted for mysql or oracle
select gp.username, gm_user_to_email.subject_source as subject_to_email_source_id, gm_user_to_email.subject_id as subject_to_email_id,
gm_user_to_email.email0 as email, to_timestamp(gp.expires_millis / 1000) as expires, gm_credential.subject_identifier0 credential_name
from grouper_password gp, grouper_members gm_user_to_email, grouper_members gm_credential
where gp.member_id_who_set_password = gm_user_to_email.id
and gm_user_to_email.email0 is not null
and to_timestamp(gp.expires_millis / 1000) > now()
and to_timestamp(gp.expires_millis / 1000) < (now() + interval '14' day)
and gm_credential.id = gp.member_id ;
A note for anyone using System.Security.Cryptography in dotnet: The private key Grouper generates is in PKCS8 format (the example is correct; the text incorrect), and dotnet's RSA.ImportFromPem(...) won't import it properly unless it has the RFC-correct preamble:
`-----BEGIN RSA PRIVATE KEY-----` - fails
`-----BEGIN PRIVATE KEY-----` - succeeds
You can also use RSA.ImportPkcs8PrivateKey() - which requires just the content of the key - instead.
1 Comment
matthew.fedder@at.internet2.edu
Jul 11, 2025A note for anyone using System.Security.Cryptography in dotnet: The private key Grouper generates is in PKCS8 format (the example is correct; the text incorrect), and dotnet's RSA.ImportFromPem(...) won't import it properly unless it has the RFC-correct preamble:
`-----BEGIN RSA PRIVATE KEY-----` - fails`
-----BEGIN PRIVATE KEY-----` - succeedsYou can also use
RSA.ImportPkcs8PrivateKey()- which requires just the content of the key - instead.