2

Let me clarify my use case:

I have a next.js application which is a plattform for listing real estate objects. I have several api routes which im using inside my next.js app. for example:

/api/createpost -> 

Takes informations from my form on my next.js app and creates a database entry to perform a new post

/api/getposts -> 

fetching all the real estate posts from my database and displays it

/api/login -> 

logs in a user by checking the credentials in the database and sends a jwt

/api/register -> 

registers a user by taking the credentials from a form from my next.js app, registering a user and creating an entry in my database


Now in order to secure my apis I want to make sure to check if there is a valid user session if anybody is calling one of the apis (except the register/login api) to get the expected result. Im doing this by calling the /api/login route and getting a valid user session. Until here everything just works fine. Apis like the /api/createpost can only be called if we have a valid user session.

Now I want to create a mobile app and I want to use my api routes from above to provide full functionality in my mobile app too. It should work the same, if i want to call the /api/createpost on my mobileapp for example, i need a valid user session.

But I want to restrict my api by asking for a key in my database which is pointing to my app and saying okay if you call the /api/createpost api, first of all i need to verify that its the mobile app asking. The mobile app will provide the key in the request then.

I didnt try this yet, but it should work i think. Now the big mess: If we call the /api/createpost and the api wants a valid token to check in the database, which will work for the mobile app, because we are giving it a valid token to check in the database, how can we provide a token if we are calling the api from inside our next.js application? Since I have to do the api call clientside, there is no way for me to provide a secret key or something to validate that the call is coming from my next.js application.

Marcel Dz
  • 2,321
  • 4
  • 14
  • 49
  • I won't put this as an answer because I don't have enough time to write code samples. What I would do is 1. Find a method for authentication like JWT, 2. Use Middleware; When you use JWT you can provide an access token to a user, whether it is in a website or a mobile application, 2. You can create a middleware function that decodes the token, gets its data, checks if the user really exists, (all that you want to check), etc. And if everything is fine, you can access to the route, otherwise you return an unauthorized error. – Herii Sep 18 '21 at 04:19
  • Please let me know if this is what you are looking for. If so, I could add a more detailed explanation as an answer. – Herii Sep 18 '21 at 04:20

2 Answers2

3

If your application is private

(to be used only by you or a few select people) You can send a private API key over SSL with each request from your application to the server and verify it. Or you can limit your API to only accept requests from certain IPs.

If your application is public

Unfortunately there's no way to determine where the request is coming from, since anything your app can send, an attacker can send it manually.
Think about it, if your app is trying to make a request to your API, any user can intercept this request before its sent out of his/her machine, and send the exact same request from a different app on the same machine.

You might say, well I can encrypt the requests and responses so that they are of no use to the attacker. But such an encryption will require either a key that's already agreed upon, or some way to provide a new key at the beginning of each session.

  1. If the key is already agreed upon, the app must contain it, as you've already guessed in the question, the attacker can retrieve this key no matter how well you try to hide it.
  2. If the encryption key is a new key provided at the beginning of each session, that's almost how SSL works, your browser handles this transaction. Your server sends a public key to your browser to encrypt the requests which the server can then decrypt with a private key. In this case you've circled back to the same problem, how can you verify to whom you give out an encryption key? What would stop an attacker from requesting the encryption key?

There has to be some way you'd be able to design apps that don't require this restriction. I think the question you should be asking isn't how to restrict your api to a certain app, but how to design apps that don't require this restriction. We might be able to help you out if you could tell us why you need this restriction.


Update

There is actually a way to verify that requests are coming from your app, but not with an api key.

For Webapps
You can use Google's reCAPTCHA to verify a user on your /register and '/login` routes, and provide an access token or start a valid user session on successful captcha response. With reCAPTCHA v3, you could even verify every user action without interrupting the user. This eliminates both the problems I mentioned in my answer above -

  1. You don't have to store an api key into the app/web app.
  2. The request can't be spoofed as it requires human user interaction within your app. The captcha verification success will arrive to your API from Google's reCAPTCHA server, not from your client app. This communication will be authenticated with a pre-mediated private API key shared by Google to you, which works in the same way as to how you authenticate your external domains.

For Android apps

A similar way to achieve the same thing would be via Android SafetyNet Attestation API. This checks the runtime environment and signs the response with a dynamically generated nonce that your app provides the SafetyNet API. Please read its docs carefully to understand how you could create potential security loopholes and how to avoid them while using this API.

For iOS apps
DeviceCheck works in a similar way, except the device validity is provided to you by Apple server.

Mythos
  • 1,378
  • 1
  • 8
  • 21
  • Thank you very much for your reply. Please check my question again, I tried to clarify more in detail where im stuck. – Marcel Dz Sep 11 '21 at 08:01
  • Thanks, your edit clarifies it a bit. But help me understand. Is your mobile key hardcoded into the app? Is it stored in an external database on the mobile? In both cases it is not secure, in fact, anyone can decompile the app bundle with a free decompiler and extract your key with little effort. – Mythos Sep 11 '21 at 08:17
  • Why do you need to verify if the request is coming from your mobile app/next.js app? Isn't it enough to validate a user session? – Mythos Sep 11 '21 at 08:18
  • thank you for trying to understand my use case. Please excuse me that im still learning a lot and maybe sometimes im asking or thinking of silly things because of my missing understanding. I have connected a PostgreSQL database to my next.js application where im saving the user informations and posts for example. i thought to create a new table there for "plattforms" or something like that. there i would save a token then which validates my mobile app. – Marcel Dz Sep 11 '21 at 08:27
  • 1
    Why do you need to verify if the request is coming from your mobile app/next.js app? Well i dont know.. because everyone can still fetch my /register and /login api right? I want to restrict it to my next.js application and my mobile app only. I dont know the common pattern, thats my problem i think. In the past I saw a plattform code which was a plattform for advertisments and they had domain name and token as key value pairs saved in the database allowing their partner sites to fetch the api giving them the credential. that seemed secure for me since only them were allowed to call the api – Marcel Dz Sep 11 '21 at 08:34
  • But at least i still have the problem that I wont be able anymore to call the api with my next.js applicaion itself, because the api asks for a valid token and domain name and I cant provide anything since I have to do the api call clientside inside my next.js application – Marcel Dz Sep 11 '21 at 08:35
  • Think of your backend as the real app, and the frontend just a convenient way for the common users to access this backend using a nice UI, because not everyone can create raw requests and poll your backend. – Mythos Sep 11 '21 at 09:25
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/236996/discussion-between-mythos-and-marcel-dz). – Mythos Sep 11 '21 at 09:26
  • @MarcelDz Please check my updated answer on various ways you can restrict your API to your apps only. This will involve much more effort to integrate into your app in a secure way than simply storing a key, but this works. – Mythos Sep 16 '21 at 22:14
  • I'd like to underline that the human interaction is key in your updated response. To sum it up you need to either authenticate the users as being a member of your website (so at worst they can send request programmatically, but those requests are authenticated so you can trace them to a precise user), or to authenticate non members as being "humans using a browser to access your app", thanks to a CAPTCHA (they are not authenticated, but they can't be easily automated). – Eric Burel Sep 17 '21 at 16:20
  • @EricBurel Yes, for webapps that's true. But the mobile app solutions check the runtime environment, so there are ways to mitigate them as well. For e.g. the iOS DeviceCheck link that I have provided, mentions that "AppAttest can't definitely pinpoint a device with compromised operating system", which means a user could use a jailbroken iOS and spoof the runtime environment as well. – Mythos Sep 17 '21 at 21:18
-1

Important edit: "secured" is not the right word here! You cannot tell that a request comes from your app just because the domain is yours. The domain name is not a safe information, as it can be altered easily. See @Mythos comments below.


Initial answer:

Web applications access is secured not based on an API key, but based on a whitelist of domains. That's how we achieve security, because only you have access to the domain where you host your own application: so the request has to be coming from an app you own.

If you try some 3rd party services that provides API for web apps, that's often how they'll work: they will let you configure a set of whitelisted domains that can access your data. If they provide you an API key, this API key is always meant to be used by a server, not a client-only app.

So if I understand you question correctly, you would do like this for each request:

  1. Check the domain. If it's in the whitelist, perfect, you can keep going. This is meant for web apps (look for "CORS").
  2. If not, check for a valid API token in the headers. This is meant for any app that can store this API token securely (another server for instance, or a mobile app in your scenario though I don't know mobile enough to tell how you store such a key)
Eric Burel
  • 3,790
  • 2
  • 35
  • 56
  • Checking the domain name is not a sufficient condition for concluding that the request is coming from your app. Its easy to forge a request with custom origin headers from terminal or even your browser with some tweakings. CORS is about protecting the client, not the API server. The API server has no way of knowing whether the request is coming from a web app or a mobile app or a terminal. – Mythos Sep 13 '21 at 19:54
  • Hi, thanks for the explanation. If you have any other learning resources about this they are more than welcome, those subjects are very important yet sadly badly understood most of the time. – Eric Burel Sep 14 '21 at 09:18
  • 1
    [Same origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) | [Ways to circumvent same origin policy](https://stackoverflow.com/questions/3076414/ways-to-circumvent-the-same-origin-policy) | [Disabling same origin policy in chrome](https://stackoverflow.com/questions/3102819/disable-same-origin-policy-in-chrome) – Mythos Sep 16 '21 at 21:35
  • 1
    [Making a simple API request from terminal](https://javarevisited.blogspot.com/2015/10/how-to-send-http-request-from-unix-or-linux-curl-wget-example.html#axzz76fLmQx9c) | [Setting custom origin header in a terminal request](https://stackoverflow.com/questions/12173990/how-can-you-debug-a-cors-request-with-curl) – Mythos Sep 16 '21 at 21:36
  • Awesome thanks a lot! – Eric Burel Sep 17 '21 at 16:12