I'm attempting to add offline token (API) access to an application. I'm looking to take an "Authorization: API $REFRESH_TOKEN" header and sending it through the oidc client to convert to an access/bearer token before having the bearer token used against Keycloak authorization services.
My problem is how do I deal with needing the Uni<Tokens> response from the oidc client without blocking and returning the Uni<SecurityIdentity> on the authenticate call. The calls that I am doing work, but I have to add a 2 second sleep, which obviously is not what I want. I often struggle with reactive programming. Can I subscribe the Uni<SecurityIdentity> to the Uni<Tokens>, or chain them.
Code
@Alternative
@Priority(1)
@ApplicationScoped
public class DexAuthenticationMechanism implements HttpAuthenticationMechanism {
@Inject
OidcAuthenticationMechanism oidc;
@Inject
OidcClients oidcClients;
ExecutorService executor = Executors.newFixedThreadPool(10, r -> {
return new Thread(r, "CUSTOM_TASK_EXECUTION_THREAD");
});
@Override
public Uni<SecurityIdentity> authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) {
String authHeader = context.request().headers().get(HttpHeaders.AUTHORIZATION);
String[] authSplit = authHeader.split("\\s+");
if (authSplit.length == 2) {
if ("BEARER".equalsIgnoreCase(authSplit[0])) {
return oidc.authenticate(context, identityProviderManager);
} else if ("BASIC".equalsIgnoreCase(authSplit[0])) {
String accessToken = "";
OidcClient basic = oidcClients.getClient("basic");
context.request().headers().set(HttpHeaders.AUTHORIZATION, "Bearer "+accessToken);
oidc.authenticate(context, identityProviderManager);
} else if ("API".equalsIgnoreCase(authSplit[0])) {
OidcClient api = oidcClients.getClient("api");
Uni<Tokens> tokensUni = api.refreshTokens(authSplit[1]);
tokensUni.runSubscriptionOn(executor)
.subscribe().with(
item -> context.request().headers().set(HttpHeaders.AUTHORIZATION, "Bearer " + item.getAccessToken())
, fail -> fail.printStackTrace()
);
System.out.println(context.request().headers().get(HttpHeaders.AUTHORIZATION));
//DON'T DO THIS
try {
Thread.sleep(2000);
}catch (Exception e){}
System.out.println("After invoke");
System.out.println(context.request().headers().get(HttpHeaders.AUTHORIZATION));
return oidc.authenticate(context, identityProviderManager);
} else {
System.out.println("Cheese and crackers");
}
} else{
System.out.println("Double cheese and crackers");
}
return Uni.createFrom().failure(new AuthenticationFailedException());
}
Config
#quarkus.oidc-client.default
quarkus.oidc-client.enabled=true
quarkus.oidc-client.auth-server-url=${keycloakAuthServerUrl}/realms/${keycloakRealm}
quarkus.oidc-client.discovery-enabled=true
quarkus.oidc-client.client-id=${keycloakResource}
quarkus.oidc-client.credentials.secret=${keycloakCredentialsSecret}
quarkus.oidc-client.grant.type=client
###quarkus.oidc-client.proxy.host=http://${squidProxyHost}
###quarkus.oidc-client.proxy.port=${squidProxyPort}
#end quarkus.oidc-client.default
#quarkus.oidc-client.default
quarkus.oidc-client."api".enabled=true
quarkus.oidc-client."api".auth-server-url=${keycloakAuthServerUrl}/realms/${keycloakRealm}
quarkus.oidc-client."api".discovery-enabled=true
quarkus.oidc-client."api".client-id=${keycloakResource}
quarkus.oidc-client."api".credentials.secret=${keycloakCredentialsSecret}
quarkus.oidc-client."api".grant.type=refresh
###quarkus.oidc-client.proxy.host=http://${squidProxyHost}
###quarkus.oidc-client.proxy.port=${squidProxyPort}
#end quarkus.oidc-client.default
Is there a way update the RoutingContext being used on the "return oidc.authenticate(context, identityProviderManager);" such that it will subscribe for the authorization header update?
So after reviewing the tags and changing smallrye to mutiny, I almost immediately found and example How can I perform via mutiny sequential calls of services?.
return tokensUni.chain(() -> oidc.authenticate(context, identityProviderManager));
If anyone knows how to flag this to the other answer directly let me know.