Authenticity of the request
The alternative is token based authentication, which works well for mobile apps, and removes the need for CSRF tokens as they themselves validates the authenticity of the request.
Auth tokens cannot be trusted to validate the authenticity of a request, because they only identify the user, not that is the genuine mobile app doing the request.
An attacker controlling the device where the mobile app is running can extract the auth token to automate the requests to the API server. Another technique used by attackers is the creation of a fake captive portal for free wifi(airports, train stations and other public spaces), where they trick who signs in to install a custom ssl certificate in their mobile device, so that the attacker can decrypt the https traffic, thus stealing the auth tokens and perform automated requests to the API server in behalf of the user.
Implementing both strategies
My current idea would be to implement both strategies but only allow token auth when origin is not present, and only allow session/cookie auth when origin header is present and allowed by the CORS policy.
This can be easily bypassed and automated by an attacker. So the attacker would
only make requests with the stolen token and without using the origin in the
headers of the request, thus avoiding the security for web and bypassing the ones
for a mobile app.
Suggestions
But I'm in now way an expert on the subject, and might easily have misunderstood something. Any suggestions or further explanations would be very welcome :)!
I will recomend you to read this series of articles about Mobile API Security techniques, that will give you good insights in how to protect an API. In the article we can see how api-keys, HMAC, certificate pinning, OAUTH can be used to secure the API and also how they can be bypassed. While in the scope of a mobile API some of the techniques are valid in the context of a web app.
For the the web:
Use the Strict Transport Policy header to guarantee that your web app is always loaded over https.
Your web app should use CSP(Content Security Policy) with a report service that will let you know in real-time when any of the policies is violated.
If using cookies you should enable httpOnly
flag to protect them from being
accessed via javascript. Further more you want to enable the secure flag for cookies to be sent only hover a https connection. Also try to scope cookies by the path they
belong to, i.e. cookies for a login page should be scoped to the /login
path,
thus they will not be sent for other pages or assets(images, css, etc.) of the the web app.
Add recAptcha V3 to all pages of your web app. It runs on the background without any user interaction required and provides a score from 0 to 1, where towards 1 we are more confident that is a human making the request.
Quoting Google:
We are excited to introduce reCAPTCHA v3, which helps you detect abusive traffic on your website without any user friction. It returns a score based on the interactions with your website and provides you more flexibility to take appropriate actions.
That score will allow to have some degree of confidence in blocking non human traffic. If you need more confidence then you may want to use also User Behaviour Analytics(UBA) solutions, that may use machine learning and artificial intelligence for further analysis of the incoming traffic and detection of abusive traffic. Due to the way the web works, both reCaptcha V3 and UBA are not able to provide bullet proof solutions to authenticate requests as legit ones.
For Mobile Apps:
Use a Mobile App Attestation solution to enable the API server to know is receiving only requests from a genuine mobile app.
The role of a Mobile App Attestation service is to guarantee at run-time that your mobile app was not tampered or is not running in a rooted device by running a SDK in the background that will communicate with a service running in the cloud to attest the integrity of the mobile app and device is running on.
On successful attestation of the mobile app integrity a short time lived JWT token is issued and signed with a secret that only the API server and the Mobile App Attestation service in the cloud are aware. In the case of failure on the mobile app attestation the JWT token is signed with a secret that the API server does not know.
Now the App must sent with every API call the JWT token in the headers of the request. This will allow the API server to only serve requests when it can verify the signature and expiration time in the JWT token and refuse them when it fails the verification.
Once the secret used by the Mobile App Attestation service is not known by the mobile app, is not possible to reverse engineer it at run-time even when the App is tampered, running in a rooted device or communicating over a connection that is being the target of a Man in the Middle Attack.
The Mobile App Attestation service already exists as a SAAS solution at Approov(I work here) that provides SDKs for several platforms, including iOS, Android, React Native and others. The integration will also need a small check in the API server code to verify the JWT token issued by the cloud service. This check is necessary for the API server to be able to decide what requests to serve and what ones to deny.
Possible Solution
So my problem is, how should I implement secure authentication for both apps?
For the web app:
- OpenID or OAUTH2 for both mobile and web app and maybe using a cookie to store the auth token, scoped by url path, with httpOnly and the secure flag enabled.
- Further use strict CSP policies along side with CORS, CSFR, Strict Transport Policy and any other I may be missing now.
- Google reCaptcha V3.
For the mobile app:
- OpenID or OAUTH2.
- Mobile App Attestation solution.
- Certificate Pinning.
The API will only accept a request that contains a header with the reCaptcha V3 token or with a mobile app attestation JWT token. Any other requests should be denied.
In the case of a web app to proceed further with the request the recCaptcha V3 score(0 to 1.0) should give the confidence that the request comes from an human, maybe greater than 0.9? You will need to play with the value and monitor it in order to find the correct balance.
For a request from a mobile app to be able to continue being processed, the API server must verify that the JWT token have a valid signature and is not expired.
While the web requests are verified in a best effort basis the requests coming from a mobile app protected with the Mobile Attestation service have only 2 possible outcomes, valid or invalid.