How to access request client ip?

I’m needing access to the user’s ip so I can do geo lookup and store what country and province the content was added from.

Not seeing anything about this in docs, only about principals and jwt claims in action metadata. What about other request headers like X-Forwarded-For?

And yeah the only metadata keys appear to be: Authorization, _kalix-jwt-claim-iat, _kalix-jwt-claim-name, _kalix-jwt-claim-sub

Thanks in advance.

I see we are missing docs for this.

The client IP header needs an opt in to be available (as it has a slight performance overhead for each request). For the JVM SDK that is done by adding x-forwarded-for to the forwardedHeaders for the component(s) where you need it.

The API for doing that is a bit cumbersome though, you will need to drop down to calling Kalix#register yourself instead of using the generated KalixFactory, something like this (in Scala, Java should look roughly but not exactly the same):

def main(args: Array[String]): Unit = {
  val kalix = Kalix()
    .register(
      MyActionProvider(
        MyAction(_),
        ActionOptions.defaults.withForwardHeaders(Set("x-forwarded-for"))
      )
    )
  kalix.start()
}

Awesome! thanks so much Johan. Though hmm, doesn’t seem to work:

First off I did have to cast back to ActionOptions:

because otherwise I got a type mismatch:

Then it compiled, but still I’m not getting the requested headers (this is from deployed service’s logs, and tried request via curl and browser both):

  override def getPost(getPostRequest: GetPostRequest): Action.Effect[Post] = {
    log.debug(s"headers: ${actionContext.metadata.getAllKeys().map(key => (key, actionContext.metadata.getAll(key)) ).mkString(" | ")}")
    ....

17:38:08.569 [kalix-akka.actor.default-dispatcher-8] DEBUG com.hiive.post.control.PostControllerServiceAction - headers: (
Authorization,Vector(Bearer <redacted>)) | (
_kalix-src,Vector(internet)) | (
_kalix-jwt-claim-aud,Vector(["https://hiive-backend-dev","https://dev-80imketoem7nz7ry.us.auth0.com/userinfo"])) | (
_kalix-jwt-claim-azp,Vector(r6ENWFTIuWx7Y79FM2xskUgaxdo9c0Z5)) | (
_kalix-jwt-claim-exp,Vector(1669569549)) | (
_kalix-jwt-claim-iat,Vector(1669483149)) | (
_kalix-jwt-claim-iss,Vector(https://dev-80imketoem7nz7ry.us.auth0.com/)) | (
_kalix-jwt-claim-scope,Vector(openid profile email)) | (
_kalix-jwt-claim-sub,Vector(google-oauth2|101152019136341878753))

Did I miss something?

Having to cast the options is an API bug/regression, I’ve added a note about that to Document opt-in forward headers · Issue #1296 · lightbend/kalix-jvm-sdk · GitHub tracking adding docs for header forwarding.

I have manually verified deploying a service with x-forwarded-for enabled via the action options and see it forwarded as expected for both gRPC and transcoded HTTP calls.

I see in your snippet that you register the same controller action provider twice (PostControllerServiceActionProvider), the setting is probably lost and replaced by the second registration of the same provider, remove that and I’d expect it starts working.

Note that the X-Forwarded-For header is not automatically present when you run locally, in integration tests or do tunneled/proxied requests to the deployed service through kalix service proxy. To try it out via those paths you’d need to pass the header yourself with the gRPC/HTTP client you use.

Oy, indeed that’s what it was, I removed the second, redundant register of that component and now I get the header. I guess that’s a downside of registering that way, no strict checking that all components are there and only once. Sorry I missed that and thanks for you keen eye Johan! -m

I think there is no actual use case for register-replace a component like that, and that it would only happen by mistake, so we should probably turn it into an exception instead.