implementing an authentication mechanism for understanding the basics of client authetication

I’m trying to implement a simple protocol to authenticate users and authorize them to access a certain web page/resource via a login form.

Please note that this is just something to use on my own and that it’s just to get the basic idea of how such systems work.

My idea is to store the hash of a shared secret between Server and client on the server side and then use a challenge-response mechanism to authenticate the user. The user would type an username and the server will respond with a challenge that implies the use of the shared secret to get access to the resource. For example, hashing the password and the challenge together and sending it back to the server. The server would do the same with the user’s stored password and check if both values are the same.

However, how can this authenticate a user A to a server B if, let’s say user C can intercept A’s response to the server and send it to the server as if it was C? Then the server would authorize C to the resource A was supposed to gain access to.

Isn’t this the way protocols such as CHAP or EAP work? (in an over-simplified way). I think I have a bit of a mess in my head, but the only solution I can think of to prevent a MiTM attack is to use TLS/SSL. But how do websites authenticate users nowadays without TLS? Also, OpenID seems to me equally vulnerable to man in the middle attacks, but it must be something I don’t understand or I’m missing.