I’m trying to learn some basic Phoenix stuff by writing a backend API and I am having trouble implementing client auth via mTLS. Maybe I am missing something about how mTLS works, but I can’t manage to force the server to authenticate the client. Is it actually possible to force the client to provide a cert to establish a connection?
The server can request that the client provide a certificate. The client can choose to submit a certificate as part of the handshake in response to such a request, but it will never send a certificate if the server didn’t ask for it.
Some servers terminate the TLS handshake if the client did not provide a valid certificate, others allow the handshake to continue and return an error in a higher protocol layer (e.g. return a 403 HTTP response).
It is not clear from your question whether the Phoenix app is the server or the client, and what is on the other end.
Keep in mind that convincing Erlang’s ssl module to accept self-signed certificates requires customising the verification logic, as by default it will want to check the certificate against a set of trusted CAs instead.
Thanks for the response. To clarify, the Phoenix app is the server. Either terminating behavior is fine for now, but I am not sure what Phoenix is capable of out of the box. I am not quite sure how to extract the client certificate information after a connection is established to determine that a 403 should be returned. Maybe I could write a Plug for it? Terminating the handshake seems easier. In trying to set this up, I didn’t see anything in the documentation that suggested how to do this, but I could have missed it if either behavior is indeed supported.
Terminating the handshake is easier and it is the default when enabling mTLS for a Phoenix server by setting verify: :verify_peer in the Endpoint options.
If you need help setting those options I think you’re going to have to share with us what you’ve tried so far, what you are using as a client and what that client is telling you…
If I simply run curl http://localhost:4000 I get a successful response back. That is a little bit of an aside since I thought that the server should be forcing ssl with the hsts option, but I can simply remove the http endpoint altogether if I want. A more direct example of what is making me scratch my head is curl -k https://localhost:4001 is returning a successful response.
A side-note on HSTS: looking at the response headers does not show that HSTS is enabled.
The verify: :verify_peer option is an HTTPS listener option, so you’ll need to move it into the https section. You’ll also have to specify the cacerts or cacertfile (on newer OTP versions you can use client_cacerts or client_cafile instead to specifically define client certificate CAs).
As I said, self-signed certs are not going to work out of the box: you would have to define how the server is supposed to know which self-signed certificates to accept and which ones to reject.
HSTS header are not sent on localhost responses, as you may want to do other testing on localhost with a different app/stack with the same browser.