My question is: How can the client securely know the API version of the backend?
As already mentioned by @Dan Pichelman a web App is easy to reverse engineer, but from your question is not clear to me if your API is serving a Web App, a Mobile App or both.
In the case a Mobile App is being used as the client it will not be so trivial as using F12 in the Web App, but tools like Xposed will make possible to reverse engineer it.
So a bullet proof solution is not possible, but for Mobile Apps you can make it more secure and difficult to reverse engineer by using some Mobile API Techniques to secure the API Key and other improvements to strength the security of your API when serving a Mobile App, like:
- Certificate Pinning to protect the communication channel between the Mobile App and the API.
- HMAC of signed/encrypted messages.
- Signed JWT Tokens with encrypted content.
- OAUTH2 as a more secure alternative for authenticate your users.
- Mobile App Attestation services.
So is there another approach I've failed to hit upon yet?
The best approach in my opinion is to not have a client that supports several Api versions, but instead to have a client that is agnostic of the back-end API version and an API back-end that is agnostic of the client using it.
How can I achieve this, may be the question in your mind now?
For me I would go with GraphpQL to achieve it. Just put a GraphQL server sitting between your existing API and your client, thus any burden of dealing with API changes will go into the GraphQL server, not into your App.
If you have not designed yet your traditional API you may go only with using GraphQL to expose your data to the clients.
A good and recent article that walks you through GraphQL may be this one, where you can see a comparison between traditional use of REST APIs and a GraphQL API. Around the start of the second third of the page you have a section about "Wrapping a REST API with GraphQL in 3 simple steps" that may help you to better understand how to use GraphQL compared with a traditional API.
Token Based Authentication
JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.
Pinning is the process of associating a host with their expected X509 certificate or public key. Once a certificate or public key is known or seen for a host, the certificate or public key is associated or 'pinned' to the host. If more than one certificate or public key is acceptable, then the program holds a pinset (taking from Jon Larimer and Kenny Root Google I/O talk). In this case, the advertised identity must match one of the elements in the pinset.
The OAuth 2.0 authorization framework enables a third-party
application to obtain limited access to an HTTP service, either on
behalf of a resource owner by orchestrating an approval interaction
between the resource owner and the HTTP service, or by allowing the
third-party application to obtain access on its own behalf. This
specification replaces and obsoletes the OAuth 1.0 protocol described
in RFC 5849.
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
Hash-based message authentication codes (or HMACs) are a tool for calculating message authentication codes using a cryptographic hash function coupled with a secret key. You can use an HMAC to verify both the integrity and authenticity of a message.
Disclaimer: I do not work or have any relation with Prisma, but I work at Approov.