Search code examples
keycloakejbwildfly

Wildfly: gRPC Server Interceptor for Keycloak oAuth Token


I have a Wildfly Server running an EJB Application, if i make a REST client call to the Application the EJB Security contex has being automatically filled up with a valid Principal and I can cast it to Keycloak Security Context and get Claim informations. The Application also accept gRCP calls, in this case the the Security context has Anonymous Principal which, I suppose, is because it has not being filled up when gRCP calls come in.

Do I have to implement a global gRPC Server Interceptor?

Is there an Example that I can follow?


Solution

  • This is probably happening because, unlike REST endpoints, gRPC doesn’t automatically integrate with Keycloak on WildFly.

    To solve this, you’ll likely need a global ServerInterceptor for gRPC to manage the authentication and authorization. This interceptor will let you extract and verify tokens from the metadata of incoming calls. Normally, you'd look for a Bearer token in the metadata headers and validate it against Keycloak.

    You may want to implement somewhat like this:

    import io.grpc.*;
    import org.keycloak.adapters.jboss.KeycloakPrincipal;
    import org.keycloak.representations.AccessToken;
    
    public class KeycloakAuthInterceptor implements ServerInterceptor {
        @Override
        public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
                ServerCall<ReqT, RespT> call,
                Metadata headers,
                ServerCallHandler<ReqT, RespT> next) {
    
            // Retrieve the Authorization header
            String token = headers.get(Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER));
            if (token == null || !token.startsWith("Bearer ")) {
                call.close(Status.UNAUTHENTICATED.withDescription("Authorization token missing"), headers);
                return new ServerCall.Listener<ReqT>() {};
            }
    
            token = token.substring("Bearer ".length());
    
            try {
                // Verify token and populate security context
                AccessToken accessToken = verifyTokenWithKeycloak(token);
                if (accessToken == null) {
                    call.close(Status.UNAUTHENTICATED.withDescription("Invalid token"), headers);
                    return new ServerCall.Listener<ReqT>() {};
                }
    
                // Populate the security context (example uses WildFly's SecurityContext)
                SecurityContext context = createSecurityContext(accessToken);
                SecurityContextAssociation.setSecurityContext(context);
    
                // Continue the gRPC call
                return Contexts.interceptCall(Context.current(), call, headers, next);
            } catch (Exception e) {
                call.close(Status.UNAUTHENTICATED.withDescription("Failed to authenticate"), headers);
                return new ServerCall.Listener<ReqT>() {};
            }
        }
    
        private AccessToken verifyTokenWithKeycloak(String token) {
            // Implement token verification with Keycloak here
            return null;  // Replace with actual token verification logic
        }
    
        private SecurityContext createSecurityContext(AccessToken accessToken) {
            // Convert AccessToken to WildFly SecurityContext with KeycloakPrincipal
            KeycloakPrincipal<?> principal = new KeycloakPrincipal<>("principal-name");
            // Set up the security context and return it
            return new SecurityContext(principal);
        }
    }