For the project I’m working on, I need to know the public IP address (the one assigned to the client’s router by its ISP) of the client that sends the request to the canister and authenticate the clients using their Internet Identity.
On the documentation I didn’t find the way to know the client’s IP address in a normal canister call. Maybe the canister_inspect_message API function could help, but I didn’t find any example and I guess that it doesn’t provide the IP address.
The only way to do so is to use the HTTP Gateway and read the
X-Real-IP header of the incoming HTTP request. I also built whats_my_ip to check if it works as expected and I can say it does (you can verify it at https://q6z4b-sqaaa-aaaam-abefa-cai.raw.ic0.app/).
The problem with HTTP inbound calls is that the call is not signed with the client’s Internet Identity and therefore I can’t know its Principal when handling the request on the canister.
As a solution, I was thinking of somehow make the client sign the HTTP call with its Internet Identity and then check the signature on the canister.
Are there other ways to achieve this? Has anyone already encountered this problem?
If you have a look at the identity integration sample you’ll see how you can use the logged in II as the caller. Here is how to get the identity of the caller.
@Severin the problem is that when I handle the inbound HTTP call with the http_request method provided by the IC HTTP Gateway protocol, there’s no way to know the Principal of the caller, as calls can be done from unauthenticated browsers and/or web2 services. The Gateway protocol is also the one that canisters use to serve web pages over HTTP, where there’s no need to authenticate the request to retrieve the page.
Since I need to know the IP address of the caller, I have to send raw HTTP requests to the canister, but then on the canister I can’t use
Principal.toText(message.caller) to get the identity of the caller.
For these reasons, should I make the caller sign in some way the raw HTTP request sent to the canister? Or am I missing something?
You could send some challenge signed with the session public key and the delegation as a payload with the request. From that you can derive the principal and verify that the delegation was indeed issued by II, matches the public key and is still valid.
However, this requires to verify a canister signature within the canister code, which is very expensive in terms of cycles.
So another approach would be to keep a bit of state around to link a canister call to an unauthenticated HTTP request. This can be done by e.g. generating a nonce in the client, sending the hash of the nonce along with the HTTP request, and (only after the HTTP request has completed) send the nonce itself using an authenticated canister call. Since nobody except the client that generated the nonce knows about it, being able to send hash(nonce) first and later reveal nonce links those two calls together.
I.e. in the canister you would need to check, that for a given nonce, a HTTP request containing hash(nonce) was received recently. If so, you can link the IP of the HTTP request to the caller of the canister call.
I hope this helps
Disclaimer: These are general, high level ideas, that I just quickly wrote down. Please think critical about my suggestions. Additional safe-guards must be added (i.e. making sure the same nonce cannot be used multiple times, etc.).