What is the best way to secure a Rest API that’s built upon PHP and used in a member-only Android and iOS app? Do I need to do OAuth or other, or is there a more low-key secure solution?
You need to define or derive security targets from requirements. This includes defining the kind of data and needed protection level. Then you can look after an appropriate solution. This can range from OAuth2 to public/private key encryption stored in the phones key store. It may require additonal 2MFA over second device at well.
For a good idea, check heavily used API Docs, like mailgun for a rock-solid approach. Also consider whether you’ll be using:
- Synchronous API where a call is made the payload is returned then or;
- Asynchronous API where a call is made then a Webhook callback fires sometime in the future.
Both have their pros and cons. Whichever pattern you choose synchronous or asynchronous, will also affect your security approach.
There is more to securing your API though. Think about your webserver. When you set up your database connection, you probably have a config file that sets up your $db that includes your database, username and password. Maybe you have an internal API set up to manage all the calls and keep your view separate from your logic. You don’t worry about the $db_name or $db_password because typically nobody can get to that unless they hack your server which let’s face it, does not happen when you have generally good practices in place and you are not a big target.
If you had two servers, one for your website and one for your database, you can still make API calls on the server side and things are protected.
But would you give your server to another person with your database passwords or API keys? No. But that is what you are doing with a native mobile application. Yes, you can create an API token that changes and submit that to your API where your data server is.
There is really good resource for this on Google’s Android Developer documentation – App security best practices, and it goes into enough detail.
You can read the docs and learn how the mechanics of setting up your endpoints and how mailgun uses basic authentication it in no way explains that you are asking about security to your DB API.
The general idea for most APIs is that your API users get their own credentials. Let’s say you have a user with a username of john.smith and a password of 12345.
The john.smith user makes one initial request to the API, providing his username/password, and the server checks to see if the credentials are correct. If they are, then the server generates a token of some kind. There are different tokens, like JWT, but for simplicity’s sake, let’s just say it’s a unique string like a GUID or something, like “f3ef72e4-6adf-4f6d-a2aa-c948dc15955e”.
Whatever that token is, it is created on the server and associated with that user in some way (e.g. a record in a database table or something saying that “john.smith” = “f3ef72e4-6adf-4f6d-a2aa-c948dc15955e”) and is then returned to the user.
From there, all the other API requests will not send username/password but will instead pass the token as part of their API requests.
So now when the server receives an API request, it can check to see if that token is valid, who it belongs to, what permissions they should have, etc… and then it can process the API request if everything is valid.
Often times these tokens are passed within cookies or HTTP headers, so the tokens don’t show up in any logs and do not make the body of the API request messy, but it’s up to you how you want to do that part.
Then after a certain period of time, the token won’t work anymore (when you generate it, you can store a date/time to indicate how long the token can work for), and the user will have to generate a new one with their credentials.
Again, there are a variety of types of tokens – JWT is a very popular format, so it’s up to you to choose the right type for your API.
Usually when people talk about hashing passwords and comparing in SQL, they’re thinking of a scenario where the password has already been hashed in the database, like the password “abc” hashing to the SHA-1 version: “a9993e364706816aba3e25717850c26c9cd0d89d”
However, part of the purpose of hashing is so that only one side (the server side) knows exactly how that hashing works. Typically, you’ll see hashing done as “salted” hashing, which means that instead of hashing “abc” and storing that value in the DB, the server first prefixes the value with its own secret value like “[email protected]#” and then hashes “[email protected]#abc” so the hash in the database is “7d58044b598281394c3a767d1144e94a71d70ed8”.
The purpose in doing this is to help protect passwords if the list of usernames/hashed passwords is ever leaked or hacked. If that were to occur, then a malicious user could decompile the app / client-side code and see the technique for hashing, and then try to run the list against a reverse hash dictionary.
But with the server-side salting, there’s now a component of that hash that the clients don’t know. So the database would have to be leaked AND that extra salt component would have to be known in order for someone to properly run an attack.
All that said, it’s usually not common to use standard salted hashes anymore. They’re not -terrible-, but there are often better ways. You have PHP as a topic zone, so you’d want to use password_hash() and password_verify() functions, which are similar in concept but use very secure methods that also automatically take advantage of stronger methods over time while staying backwards compatible.
Now, as far as hashing things on the client side goes, I’ll note that there’s still -some- value in it, but not related in any way to how the hashing is stored in the database.
Presuming you’re using HTTPS for these API requests (you should be), as long as you’re not sending the credentials inside the query string (never send any sensitive info over in a query string – query strings are usually part of web access logs), it should generally be okay. The credentials being sent over in the headers or in the body will be encrypted (and if you think about it, that’s how we log into most web applications today via our browsers). That said, if there’s any concern with this (e.g. MITM proxies, like corporate firewalls that intercept traffic for inspection and can decrypt because they are posing as the endpoint), you can always hash the password so that the user’s real password isn’t sent over.
The server wouldn’t really care – it would simply think someone happened to type in 8b476a33a779b52582d13e4f7d85a1e6cd99f72a (or whatever the resulting hash was) as their password. The main advantage is going to be in those proxy scenarios. If a user happens to reuse the same password over and over again for all their apps, and if there WAS an SSL-intercepting proxy in play, then a malicious system admin couldn’t see the original password and then retry that user’s credentials somewhere else. And if you use a really long, obscure salt when hashing on the app side, even if they decompiled the app and got the salt, it would be difficult to find a match in any of the hash-lookup dictionaries.
So again, it’s all about layers and how many you want to add.