In the UDP protocol, a socket is uniquely identified by the source IP and the source port.
In the TCP protocol, the socket is uniquely identified by the source IP, source port, destination IP, and destination port. Why does the TCP protocol require two extra pieces of information
NB for TCP terminology, the socket is the address-port pair; a pair of sockets defines the connection. (Per RFC 793 p5)
I'm afraid you're mistaken about UDP, which although it doesn't really have "sockets" -- even if the Berkeley Sockets library calls them that, and it's a reasonable thing to call an address-port pair -- multiplexes in essentially the identical way to TCP.
A typical situation where you can see this is the case is simultaneous multiple DNS resolutions from one host to the same DNS server, where clearly only the source port number is necessarily different. You can see this is exactly the same situation as multiple simultaneous TCP connections from one client to a single web server.
UDP has connectionless datagrams. Host A sends the datagram out of an address-port pair, directed at an address-port pair at B, which typically, but not always, replies in the mirror way. Speaking more loosely of the "communication", it operates over exactly the same 4-tuple as a TCP connection.
You will sometimes see reference to a 5-tuple of (procotol, source address, source port, destination address, destination port), where protocol would be 17 for UDP, 6 for TCP etc. This is what most firewalls, routers etc use for NAT and similar operations to identify this communicating pair.
even though the server has established a TCP socket specifically for that session on a different port
I'm afraid you're also mistaken about TCP, possibly because of the conflict of terminology between the definition of the TCP protocol (RFC 793) and its most common practical implementation, the Berkeley Sockets Library, as used in Unix and everything descended from it.
If you focus on the protocol it's much clearer: there is no "different port". The web server is only listening on, for example, 184.108.40.206 port 80. The client only sends from, for illustration 220.127.116.11 port 56789. Every single packet will be 18.104.22.168:80 to 22.214.171.124:56789 or vice versa; easily verified by looking at packets with tcpdump/wireshark/etc.
(To very briefly digress to the Berkeley implementation, a TCP connection is represented by an integer typically but confusingly called
sockfd; a TCP socket is represented by a
struct sockaddr. The
accept() system call very confusingly speaks of making a "new connected socket", by which it means new connection structure in the connected state. The tuple of this resulting thing would be in our example (126.96.36.199, 80, 188.8.131.52, 56789). Regarding UDP, the library allows you to consider UDP as connected, which is a convenient if completely wrong way of describing UDP datagram exchange between two processes, and just means the structure remembers the far address-port pair, which in programming terms makes the UDP "connection" look just like a TCP one. Remember the Berkeley library is not just for IP, and has generalisations of several different underlying networking systems. If you want to follow up these network programming terms I suggest Stack Overflow, which has many very competent network programmers.)