Keycloak integration to external identity provider fails when validation tokens with JWKS URL - jwt

I'm configuring an external identity provider in my Keycloak instance and trying to get it to validate the tokens using a external JWKS URL. Using the converted PEM from JWKS works fine, the using the URL is not working.
The token validation fails upon login with the following message:
[org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider] (default task-4) Failed to make identity provider oauth callback: org.keycloak.broker.provider.IdentityBrokerException: token signature validation failed
I debugged the Keycloak server get more on the problem and found a "problem" in class JWKSUtils:
/**
* #author Marek Posolda
*/
public class JWKSUtils {
//...
public static Map<String, KeyWrapper> getKeyWrappersForUse(JSONWebKeySet keySet, JWK.Use requestedUse) {
Map<String, KeyWrapper> result = new HashMap<>();
for (JWK jwk : keySet.getKeys()) {
JWKParser parser = JWKParser.create(jwk);
if (jwk.getPublicKeyUse().equals(requestedUse.asString()) && parser.isKeyTypeSupported(jwk.getKeyType())) {
KeyWrapper keyWrapper = new KeyWrapper();
keyWrapper.setKid(jwk.getKeyId());
keyWrapper.setAlgorithm(jwk.getAlgorithm());
keyWrapper.setType(jwk.getKeyType());
keyWrapper.setUse(getKeyUse(jwk.getPublicKeyUse()));
keyWrapper.setVerifyKey(parser.toPublicKey());
result.put(keyWrapper.getKid(), keyWrapper);
}
}
return result;
}
//...
}
The if fails with a NullPointerException because the call jwk.getPublicKeyUse() returns null.
But I found out that it's null because the JWKS URL returns a single key without the attribute use, which is optional according to the specification. [https://www.rfc-editor.org/rfc/rfc7517#section-4.2]
Keycloak only accepts JWKS URLs that return all keys with the attribute use defined. But the IdP I'm trying to connect does not return that attribute in the key.
Given that situation, to who should I file an issue, the IdP or to Keycloak? Or is there something I'm doing wrong in the configuration?

I filed an issue with Keycloak about this exact problem in August 2019.
Their answer:
Consuming keys without validating alg and use is dangerous as such
Keycloak requires these to be present.
In my case, I contacted the IdP and they were able to populate the "use" parameter. If that is not an option, then you're pretty much stuck with your workaround.

Related

HttpAlbIntegration (APi Gateway) with cognito custom claims

I'm using the HttpApi (Api Gateway) with a HttpAlbIntegration to connect to my backend. My backend requires a X-API-KEY header with a value comming from a custom claim in the JWT. So my setup is something like this:
const integration = new HttpAlbIntegration(`http-alb-integration`, albListener, {
method: HttpMethod.ANY,
vpcLink: vpcLinkStack.vpcLink,
parameterMapping: new ParameterMapping()
.appendHeader('x-api-key', MappingValue.contextVariable("authorizer.claims['custom:api_key']")
});
According to this https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-enable-cognito-user-pool.html, it should work, but cdk complains about:
Invalid mapping expression specified: Validation Result: warnings : [], errors :
[Invalid mapping expression specified: $context.authorizer.claims['custom:api_key']]
All the other claims work fine: email and sub for instance. The problem only happens with custom attributes, I'm guessing because of the sintax that includes a :. I've tried different sintax alternatives, no luck. The straight question is:
how can I access custom claims from cognito authorizer in HttpApi integration?

CXF STSClient asks for user+password even though there is a SAML token in onBehalfOf

I'm using CXF's STSClient to request a JWT token on behalf of a user so I can call a REST service.
I have a valid SAML token and tried to configure the STSClient like so:
stsClient.setTokenType("urn:ietf:params:oauth:token-type:jwt");
stsClient.setKeyType("http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer");
stsClient.setOnBehalfOf(samlToken.getToken());
stsClient.setEnableAppliesTo(true);
// Not sure about these.
stsClient.setSendRenewing(false);
stsClient.setKeySize(0);
stsClient.setRequiresEntropy(false);
final Map<String, Object> requestContext = Preconditions.checkNotNull(stsClient.getRequestContext());
requestContext.put(SecurityConstants.USERNAME, name); // Without this, I get "No username available"
SecurityToken result = stsClient.requestSecurityToken(appliesTo);
but when the method fails with:
Caused by: org.apache.cxf.interceptor.Fault: No callback handler and no password available
at org.apache.cxf.ws.security.wss4j.policyhandlers.TransportBindingHandler.handleBinding(TransportBindingHandler.java:182)
at org.apache.cxf.ws.security.wss4j.PolicyBasedWSS4JOutInterceptor$PolicyBasedWSS4JOutInterceptorInternal.handleMessageInternal(PolicyBasedWSS4JOutInterceptor.java:180)
at org.apache.cxf.ws.security.wss4j.PolicyBasedWSS4JOutInterceptor$PolicyBasedWSS4JOutInterceptorInternal.handleMessage(PolicyBasedWSS4JOutInterceptor.java:110)
at org.apache.cxf.ws.security.wss4j.PolicyBasedWSS4JOutInterceptor$PolicyBasedWSS4JOutInterceptorInternal.handleMessage(PolicyBasedWSS4JOutInterceptor.java:97)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:308)
at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:530)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:441)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:356)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:314)
at org.apache.cxf.ws.security.trust.AbstractSTSClient.issue(AbstractSTSClient.java:874)
at org.apache.cxf.ws.security.trust.STSClient.requestSecurityToken(STSClient.java:71)
at org.apache.cxf.ws.security.trust.STSClient.requestSecurityToken(STSClient.java:65)
at org.apache.cxf.ws.security.trust.STSClient.requestSecurityToken(STSClient.java:61)
Caused by: org.apache.cxf.ws.policy.PolicyException: No callback handler and no password available
at org.apache.cxf.ws.security.wss4j.policyhandlers.AbstractCommonBindingHandler.unassertPolicy(AbstractCommonBindingHandler.java:93)
at org.apache.cxf.ws.security.wss4j.policyhandlers.AbstractBindingBuilder.getPassword(AbstractBindingBuilder.java:1042)
at org.apache.cxf.ws.security.wss4j.policyhandlers.AbstractBindingBuilder.addUsernameToken(AbstractBindingBuilder.java:839)
at org.apache.cxf.ws.security.wss4j.policyhandlers.TransportBindingHandler.addSignedSupportingTokens(TransportBindingHandler.java:115)
at org.apache.cxf.ws.security.wss4j.policyhandlers.TransportBindingHandler.handleNonEndorsingSupportingTokens(TransportBindingHandler.java:208)
at org.apache.cxf.ws.security.wss4j.policyhandlers.TransportBindingHandler.handleBinding(TransportBindingHandler.java:167)
... 16 common frames omitted
Since I have a SAML token, I was expecting that the STSClient doesn't need the user name or password anymore.
How can I tell CXF / STSClient to skip the addUsernameToken() method call?
The problem is in the WSDL definition of the service.
Each port in the WSDL is attached to a URL and a binding. The binding has a policy. The policy in this case requests a user name with password. Look for UsernameToken in the WSDL.
What you need is a port that doesn't require this. I'm no expert in this matter but from the examples I've seen, the policy must not have a SignedSupportingTokens element in them, only Wss11 and Trust13 elements.
Without this element, CXF will take a different path in the code and the error will go away.

How do I load a "user" in a micronaut backend when JWT is provided

I have a Micronaut microservice that handles authentication via JsonWebTokens (JWT) from this guide.
Now I'd like to extend this code. The users in my app have some extra attributes such as email, adress, teamId etc. I have all users in the database.
How do I know in the backend controller method which user corresponds to the JWT that is sent by the client?
The guide contains this example code for the Micronaut REST controller:
#Secured(SecurityRule.IS_AUTHENTICATED)
#Controller
public class HomeController {
#Produces(MediaType.TEXT_PLAIN)
#Get
public String index(Principal principal) {
return principal.getName();
}
}
I know that I can get the name of the principal, ie. the username from the HttpRequest. But how do I get my additional attributes?
(Maybe I misunderstand JWT a bit???)
Are these JWT "claims" ?
Do I need to load the corresponding user by username from my DB table?
How can I verify that the sent username is actually valid?
edit Describing my usecase in more detail:
Security requirements of my use case
Do not expose valid information to the client
Validate everything the client (a mobile app) sends via REST
Authentication Flow
default oauth2 flow with JWTs:
Precondition: User is already registerd. Username, hash(password) and furhter attributes (email, adress, teamId, ..) are known on the backend.
Client POSTs username and password to /login endpoint
Client receives JWT in return, signed with server secret
On every future request the client sends this JWT as bearer in the Http header.
Backend validates JWT <==== this is what I want to know how to do this in Micronaut.
Questions
How to validate that the JWT is valid?
How to and where in which Java class should I fetch additional information for that user (the additional attributes). What ID should I use to fetch this information. The "sub" or "name" from the decoded JWT?
How do I load a “user” in a micronaut backend when JWT is provided?
I am reading this as you plan to load some kind of User object your database and access it in the controller.
If this is the case you need to hook into the place where Authentication instance is created to read the "sub" (username) of the token and then load it from the database.
How to extend authentication attributes with more details ?
By default for JWT authentication is created using JwtAuthenticationFactory and going more concrete default implementation is DefaultJwtAuthenticationFactory. If you plan to load more claims this could be done by replacing it and creating extended JWTClaimsSet or your own implementation of Authentication interface.
How do I access jwt claims ?
You need to check SecurityService -> getAuthentication() ->getAttributes(), it returns a map of security attributes which represent your token serialised as a map.
How to validate that the JWT is valid?
There is a basic validation rules checking the token is not expired and properly signed, all the rest validations especially for custom claims and validating agains a third parties sources have to be done on your own.
If you plan to validate your custom claims, I have already open source a project in this scope, please have a look.
https://github.com/traycho/micronaut-security-attributes
How to extend existing token with extra claims during its issuing ?
It is required to create your own claims generator extending JWTClaimsSetGenerator
#Singleton
#Replaces(JWTClaimsSetGenerator)
class CustomJWTClaimsSetGenerator extends JWTClaimsSetGenerator {
CustomJWTClaimsSetGenerator(TokenConfiguration tokenConfiguration, #Nullable JwtIdGenerator jwtIdGenerator, #Nullable ClaimsAudienceProvider claimsAudienceProvider) {
super(tokenConfiguration, jwtIdGenerator, claimsAudienceProvider)
}
protected void populateWithUserDetails(JWTClaimsSet.Builder builder, UserDetails userDetails) {
super.populateWithUserDetails(builder, userDetails)
// You your custom claims here
builder.claim('email', userDetails.getAttributes().get("email"));
}
}
How do I access jwt claims ?
If you want to access them from the rest handler just add io.micronaut.security.authentication.Authentication as an additional parameter in the handling method. Example
#Get("/{fooId}")
#Secured(SecurityRule.IS_AUTHENTICATED)
public HttpResponse<Foo> getFoo(long fooId, Authentication authentication) {
...
}
I found a solution. The UserDetails.attributes are serialized into the JWT. And they can easily be set in my CustomAuthenticationProviderclass:
#Singleton
#Slf4j
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
public Publisher<AuthenticationResponse> authenticate(
#Nullable HttpRequest<?> httpRequest,
AuthenticationRequest<?, ?> authenticationRequest)
{
// ... autenticate the request here ...
// eg. via BasicAuth or Oauth 2.0 OneTimeToken
// then if valid:
return Flowable.create(emitter -> {
UserDetails userDetails = new UserDetails("sherlock", Collections.emptyList(), "sherlock#micronaut.example");
// These attributes will be serialized as custom claims in the JWT
Map attrs = CollectionUtils.mapOf("email", email, "teamId", teamId)
userDetails.setAttributes(attrs);
emitter.onNext(userDetails);
emitter.onComplete();
}, BackpressureStrategy.ERROR);
}
}
And some more pitfalls when validating the JWT in the backend
A JWT in Micronaut MUST contain a "sub" claim. The JWT spec does not require this, but Micronaut does. The value of the "sub" claim will become the username of the created UserDetails object.
If you want to load addition attributes into these UserDetails when the JWT is validated in the backend, then you can do this by implementing a TokenValidator. But (another pitfal) then you must set its ORDER to a value larger than micronaut's JwtTokenValidator. Your order must be > 0 otherwise your TokenValidator will not be called at all.

Websphere Commerce Custom REST service for Login using social sign in not generating valid WC Tokens and CTXMGMT table not getting updated

In the current website, social login is implemented using the mapping in struts and it will call the custom controller command "XYZThirdPartyLoginCmdImpl" which will authenticate the details passed and it will call the out of the box "LogonCmd" for login.
For creating a REST service for the above functinality, created a custom REST handler " XYZThirdPartyLoginHandler" and from there called the existing command "XYZThirdPartyLoginCmdImpl" using the method executeControllerCommandWithContext. Once the response is generated, WCToken and WCTrustedToken is generated by the below code.
ActivityToken token = getActivityToken();
String identitySignature = token.getSignature();
String identityId = token.getActivityGUID().getGUID().toString();
Map<String, Object> identityTokenInfo = new HashMap();
identityTokenInfo.put(MemberFacadeConstants.EC_USERID, new String[] { userId.toString() } );
identityTokenInfo.put(MemberFacadeConstants.ACTIVITY_TOKEN_ID, new String[] { identityId } );
identityTokenInfo.put(MemberFacadeConstants.ACTIVITY_TOKEN_SIGNATURE, new String[] { identitySignature } );
Map<String, String> commerceTokens = CommerceTokenHelper.generateCommerceTokens(identityTokenInfo);
String wcToken = commerceTokens.get(CommerceTokenHelper.WC_TOKEN);
String wcTrustedToken = commerceTokens.get(CommerceTokenHelper.WC_TRUSTED_TOKEN);
The tokens generated using this is not valid. If we try to invoke any other rest service using this token it shows invalid user session error. "XYZThirdPartyLoginCmdImpl" authentication is success as the userId returned is correct. After executing this the user context is not getting created in CTXMGMT table.
Please guide on how to generate the valid tokens in REST flow in this use case.
If you are on v9, you might want to investigate the oauth_validate REST call (/wcs/resources/store//loginidentity/oauth_validate). See the KC article for more information: [https://www.ibm.com/support/knowledgecenter/SSZLC2_9.0.0/com.ibm.commerce.integration.doc/tasks/tcv_sociallogin.htm][1]. This calls some different commands (OAuthTokenValidationCmdImpl and OpenUserRegisterCmd) than what you might be using, but it allows you to pass in a 3rd party token, and it generates the right tokens.

Auth 0 configuration audience

I just found out that I have a problem with auth0 and it relates to the auth0 configuration audience. So when I explicitly write the audience, the JWT verification failed with error The provided Algorithm doesn't match the one defined in the JWT's Header. When I don't write the audience, everything will work fine, except now everytime the token expire and user click on login link it skip the login process and immediately logged in with the previous credential. I don't want this to happen, I want user to still authenticate themselves again after token expire, just like when I write the audience.
So what is audience and why does it affect the behaviour like this?
And How can I fix it to get the behaviour I wanted?
Below is the configuration of the Auth0
auth0 = new auth0.WebAuth({
clientID: environment.auth0ClientId,
domain: environment.auth0Domain,
responseType: 'token id_token',
//Below is the audience I'm talking about
audience: '${constants.MY_APP}/userinfo',
redirectUri: `${constants.ORIGIN_URL}/auth`,
scope: 'openid email'
});
I need to know how I can make the JWT to be verified correctly as well as make the login behaviour correctly when the JWT expire.
Auth0 can issue two types of tokens: opaque and JWT.
When you specify the audience parameter, you will receive a JWT token. JWTs differ from opaque tokens in that they are self-contained and therefore you verify them directly in your application.
In this case, the JWT you have received is signed with an algorithm different to that which you've defined in your verification logic. You can decode the JWT using https://jwt.io and you can see which algorithm it was signed with in the alg attribute of the header.
You can also find out the signing algorithm your API uses in the Auth0 dashboard. Go APIs, click your API, click the Settings tab and then scroll to Token Setting. You will see it listed as the Signing Algorithm.
Judging by the error message, you are using the java-jwt library, in which case you will need change the signing algorithm accordingly per the steps outlined here: https://github.com/auth0/java-jwt#verify-a-token
For HS256:
try {
Algorithm algorithm = Algorithm.HMAC256("secret");
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("auth0")
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception){
//Invalid signature/claims
}
Where secret is your API's Signing Secret.
For RS256, it's a little more involved. You first need to decode the token to retrieve the kid (key ID) from the header:
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {
DecodedJWT jwt = JWT.decode(token);
} catch (JWTDecodeException exception){
//Invalid token
}
You then need to construct a JwkProvider using the jwks-rsa-java library:
JwkProvider provider = new UrlJwkProvider("https://your-domain.auth0.com/");
Jwk jwk = provider.get(jwt.getKeyId());
Finally, you can use the public key retrieved from the JWKS and use it to verify the token:
RSAPublicKey publicKey = (RSAPublicKey) jwk.getPublicKey();
try {
Algorithm algorithm = Algorithm.RSA256(publicKey, null);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("auth0")
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception) {
//Invalid signature/claims
}
Keep in mind that it's preferred to use RS256 over HS256 for the reasons outlined here: https://auth0.com/docs/apis#signing-algorithms
You may also find this article useful for detailed information on verifying tokens: https://auth0.com/docs/api-auth/tutorials/verify-access-token