Deehaz05
Deehaz05

Reputation: 51

How do i pass a Bearer Token dynamically with Quarkus Microprofile Rest Client?

I'm trying to call a Keycloak Admin REST endpoint to create a keycloak user in my code and it requires the request to have a Bearer token added to the Authorization header. I need a way to pass the token dynamically to the rest client.

I have to call another service to generate the token.

Here's what I've tried and the error that i got. What could be wrong or is there a better way to do this?

SignUpService.java

@ApplicationScoped
public class SignUpService {
    private static final Logger LOG = Logger.getLogger(SignUpService.class);

    @Inject
    @RestClient
    KeycloakClientAdmin keycloakClientAdmin;

    public Response create(KeycloakUserDTO keyCloakUser) {
    ...
    try {
            keycloakClientAdmin.createKeycloakUser(keyCloakUser);
        } catch (Exception e) {
            LOG.info("Unable to create new user");
            e.printStackTrace();
        }
    }
    return Response.ok().build();
}

KeycloakClientAdmin.java

@Path("/")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RegisterRestClient(configKey = "config.api.keycloak.admin")
@RegisterClientHeaders(KeycloakClientHeader.class)
public interface KeycloakClientAdmin {
    
    @POST
    @Path("/admin/realms/{realm}/users")
    public void createKeycloakUser(keycloakUserDTO user);
}

KeycloakClient.java

@Path("/")
@Produces(MediaType.APPLICATION_JSON)
@RegisterRestClient(configKey = "config.api.keycloak.token")
public interface KeycloakClient {
    

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Path("/realms/master/protocol/openid-connect/token")
    public TokenRepresentation createToken(MultivaluedMap<String, String> formMap);
}

KeycloakClientHeader.java

The token was succesfully created here,I was able to log it, but from the error(status code 401) i got it looks like it wasn't added to the header. The stack trace is at the end.

@ApplicationScoped
public class KeycloakClientHeader implements ClientHeadersFactory{
    private static final Logger LOG = Logger.getLogger(KeycloakClientHeader.class);

    @Inject
    @RestClient
    KeycloakClient keycloakClient;

    @Override
    public MultivaluedMap<String, String> update(
                          MultivaluedMap<String, String> mm1,MultivaluedMap<String, String> mm2) {
        MultivaluedMap<String, String> result = new MultivaluedMapImpl<>();
        MultivaluedMap<String, String> tokenRequest = new MultivaluedMapImpl<>();

        tokenRequest.add("client_id", "admin-cli");
        tokenRequest.add("username", admin);
        tokenRequest.add("password", password);
        tokenRequest.add("grant_type", "password");
        
        String token = null;   
        try {
            token = keycloakClient.createToken(tokenRequest).getAccess_token();
            LOG.info("Token: " + token);
        } catch (Exception e) {
            LOG.info("Unable to create token");
            e.printStackTrace();
        }
        result.add("Authorization", "Bearer  " + token);
        return result;
    }
}

application.properties file

config.api.keycloak.token/mp-rest/url=http://localhost:9000
config.api.keycloak.token/mp-rest/scope=javax.inject.Singleton

config.api.keycloak.admin/mp-rest/url=http://localhost:9000
config.api.keycloak.admin/mp-rest/scope=javax.inject.Singleton

The error:

Unable to create new user
--
org.jboss.resteasy.client.exception.ResteasyWebApplicationException: Unknown error, status code 401
--
        at org.jboss.resteasy.client.exception.WebApplicationExceptionWrapper.wrap(WebApplicationExceptionWrapper.java:107)
--
        at org.jboss.resteasy.microprofile.client.DefaultResponseExceptionMapper.toThrowable(DefaultResponseExceptionMapper.java:21)
--
        at org.jboss.resteasy.microprofile.client.ExceptionMapping$HandlerException.mapException(ExceptionMapping.java:41)
--
        at org.jboss.resteasy.microprofile.client.ProxyInvocationHandler.invoke(ProxyInvocationHandler.java:153)
--
        at com.sun.proxy.$Proxy157.createKeycloakUser(Unknown Source)
--
        at farm.everyfarmer.mobile.useraccount.service.SignUpService.create(SignUpService.java:94)
--
        at farm.everyfarmer.mobile.useraccount.service.SignUpService_Subclass.create$$superforward1(SignUpService_Subclass.zig:137)
--
        at farm.everyfarmer.mobile.useraccount.service.SignUpService_Subclass$$function$$2.apply(SignUpService_Subclass$$function$$2.zig:33)
--
        at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
--
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:62)
--
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:49
--
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
--
        at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
--
        at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
--
        at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
--
        at farm.everyfarmer.mobile.useraccount.service.SignUpService_Subclass.create(SignUpService_Subclass.zig:293)
--
        at farm.everyfarmer.mobile.useraccount.service.SignUpService_ClientProxy.create(SignUpService_ClientProxy.zig:157)
--
        at farm.everyfarmer.mobile.useraccount.resource.SignUpResource.createAcccount(SignUpResource.java:45)
--
        at farm.everyfarmer.mobile.useraccount.resource.SignUpResource_Subclass.createAcccount$$superforward1(SignUpResource_Subclass.zig:94)
--
        at farm.everyfarmer.mobile.useraccount.resource.SignUpResource_Subclass$$function$$1.apply(SignUpResource_Subclass$$function$$1.zig:33)
--
        at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
--
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:62)
--
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:49)
--
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
--
        at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
--
        at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
--
        at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
--
        at farm.everyfarmer.mobile.useraccount.resource.SignUpResource_Subclass.createAcccount(SignUpResource_Subclass.zig:158)
--
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
--
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
--
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
--
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
--
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:170)
--
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130)
--
        at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:660)
--
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:524)
--
        at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:474)
--
        at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
--
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:476)
--
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:434)
--
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:408)
--
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:69)
--
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:492)
--
        at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:261)
--
        at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:161)
--
        at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
--
        at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:164)
--
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:247)
--
        at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:73)
--
        at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:138)
--
        at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler$1.run(VertxRequestHandler.java:93)
--
        at io.quarkus.vertx.core.runtime.VertxCoreRecorder$13.runWith(VertxCoreRecorder.java:536)
--
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
--
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
--
        at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
--
        at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
--
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
--
        at java.base/java.lang.Thread.run(Thread.java:829)

Upvotes: 3

Views: 6256

Answers (2)

Arthur
Arthur

Reputation: 608

Alternatively, you could just add the keycloak-admin-client module dependency to your project and you don't have to worry about managing most of what you are doing yourself. It is done for you.

Upvotes: 1

jcompetence
jcompetence

Reputation: 8413

Your mistake is having double space, instead of 1 in bearer:

 HttpHeaders.AUTHORIZATION, "Bearer " + tokens.getAccessToken());

vs

 result.add("Authorization", "Bearer  " + token); <-- Has double space

Fix: result.add("Authorization", "Bearer " + token);

The specification is clear that it should be 1 Single Space:


2.1. Authorization Request Header Field

When sending the access token in the "Authorization" request header field defined by HTTP/1.1 [RFC2617], the client uses the "Bearer"
authentication scheme to transmit the access token.

For example:

 GET /resource HTTP/1.1
 Host: server.example.com
 Authorization: Bearer mF_9.B5f-4.1JqM

The syntax of the "Authorization" header field for this scheme
follows the usage of the Basic scheme defined in Section 2 of
[RFC2617]. Note that, as with Basic, it does not conform to the
generic syntax defined in Section 1.2 of [RFC2617] but is compatible
with the general authentication framework being developed for HTTP 1.1 [HTTP-AUTH], although it does not follow the preferred practice outlined therein in order to reflect existing deployments. The syntax for Bearer credentials is as follows:

 **b64token    = 1*( ALPHA / DIGIT /
                   "-" / "." / "_" / "~" / "+" / "/" ) *"="
 credentials = "Bearer" 1*SP b64token**

https://datatracker.ietf.org/doc/html/rfc6750#section-2.1

Upvotes: 0

Related Questions