The three-time world champion started the race on pole but Vettel overtook his rival on the first corner. Hamilton fought back however, retaking the lead on lap 44 to seal his second win of the season.
Red Bull's Daniel Ricciardo finished in third place ahead of Force India's Sergio Perez, with his teammate Esteban Ocon finishing fifth.
Vettel took control of a gripping race after sweeping around the outside of the Mercedes driver on the first turn of the afternoon, opening up a two second lead after the first lap. A smart strategic move by Mercedes ensured their driver remained in second and within touching distance of the leader throughout, with Hamilton's late attack deciding their duel.
Hamilton now sits six points off Vettel in the Drivers' Championship after five races.
"That is how racing should be today. Seb was really fast," Hamilton said post-race. "The start was not good enough, I got a bit of wheelspin. I will have to talk to my guys about that."
On slipping behind Vettel, he added: "I felt like I ran out of road and I was on the outside but that is racing and I wouldn't have changed it for the world"
Behind the two, there was chaos as Valtteri Bottas bumped into Kimi Raikkonen, sending the Ferrari driver crashing into Max Verstappen on lap 12. It forced the Red Bull driver to the pits where he ended his race, with Raikkonen's front suspension left in tatters, also forcing him off the track.
Fernando Alonso, competing in his last race before his switch to the Indy 500 which will see him miss the Monaco Grand Prix at the end of the month, started the afternoon in seventh place, but slipped four places behind after he was eased off the track by Williams' Felipe Massa on turn two.