Generally, this is done with some sort of bastion host that has access to both networks (internet and intranet). At the simplest, you allow SSH access to the bastion host (for authenticated users only, of course) and then people tunnel through the bastion to reach the internal hosts. That requires setting up SSH tunnels, though, which isn't always convenient or user-friendly. An alternative is to have a login web app on the bastion (or even just use something like TLS mutual authentication with client certificates), and after the user is authenticated the bastion acts as an invisible or reverse proxy, taking requests meant for the back-end hosts and getting their responses, then passing those responses back to the (authenticated) users via a secure connection (probably just HTTPS, unless you're using a non-HTTP protocol in which case you might have to get fancier).
You could also use a VPN server to bridge into the intranet. Users authenticate to the VPN, after which it's basically as though they are on the same local network the VPN server is on (i.e. the same one as your internal services) until the VPN tunnel is closed or broken. There are a number of VPN technologies and they aren't all equally secure (anything using MS-CHAPv2, for example, is pretty easy to break), but most OSes support a number of VPN technologies out of the box, and there's also third-party software like OpenVPN (F/LOSS) or various commercial options.
The bastion host / VPN server, being a bridge between sensitive and untrusted networks, should of course be as hardened as possible. Don't run any more services on it than needed (good advice for any server, really), keep the program logic running on it minimal (to minimize attack surface; no need to by fancy), and so on. Keep it updated, maintain audit logs, and all that good stuff.