You should use this option if you wish to protect access to 3rd party or managed APIs where you are not able to add an Approov token check to the backend. This allows client secrets, or API keys, used for access to be protected with Approov. Rather than build secrets into an app where they might be reverse engineered, they are only provided at runtime by Approov for apps that pass attestation. This substantially improves your protection and prevents these secrets being abused by attackers. Where you are able to modify the backend we recommend you use API Protection for further enchanced flexibility and security.
This quickstart provides straightforward implementation if the secret is currently supplied in a request header to the API. The quickstart interceptor is able to automatically rewrite headers as the requests are being made, to automatically substitute in the secret, but only if the app has passed the Approov attestation checks. If the app fails its checks then you can add a custom rejection handler.
These additional steps require access to the Approov CLI, please follow the Installation instructions.
Client secrets or API keys also need to be protected in transit. For 3rd party APIs you should not pin against their certificates since you are not in control of when they might changed. Instead the Managed Trust Roots feature can be used to protect TLS.
Ensure managed trust roots are enabled using:
approov pin -setManagedTrustRoots on
Note that this command requires an admin role.
This ensures connections may only use official certificates, and blocks the use of self signed certificates that might be used by a Man-in-the-Middle (MitM) attacker.
In order for secrets to be protected for particular API domains it is necessary to inform Approov about them. Execute the following command:
approov api -add <your-domain> -noApproovToken
This informs Approov that it should be active for the domain, but does not need to send Approov tokens for it. Adding the domain ensures that the channel will be protected against Man-in-the-Middle (MitM) attacks.
It is assumed that you already have some client secrets and/or API keys in your app that you would like to migrate for protection by Approov. To do this you first need to enable the Secure Strings feature:
approov secstrings -setEnabled
Note that this command requires an admin role.
The quickstart integration works by allowing you to replace the secret in your app with a placeholder value instead, and then the placeholder value is mapped to the actual secret value on the fly by the interceptor (if the app passes Approov attestation). The shipped app code will only contain the placeholder values.
If your app currently uses <secret-value>
then replace it in your app with the value <secret-placeholder>
. Choose a suitable placeholder name to reflect the type of the secret. The placeholder value will be added to requests in the normal way, but you should be using the Approov enabled networking client to perfom the substituion.
You must inform Approov that it should substitute <secret-placeholder>
for <secret-value>
in requests as follows:
approov secstrings -addKey <secret-placeholder> -predefinedValue <secret-value>
Note that this command also requires an admin role.
You can add up to 16 different secret values to be substituted in this way.
If the secret value is provided on the header <secret-header>
then it is necessary to notify the ApproovService
that the header is subject to substitution. You do this by making the call once, after initialization:
ApproovService.addSubstitutionHeader("<secret-header>", null)
With this in place the Approov interceptor should replace the <secret-placeholder>
with the <secret-value>
as required when the app passes attestation. Since the mapping lookup is performed on the placeholder value you have the flexibility of providing different secrets on different API calls, even if they passed with the same header name.
Since earlier released versions of the app may have already leaked the <secret-value>
, you may wish to refresh the secret at some later point when any older version of the app is no longer in use. You can of course do this update over-the-air using Approov without any need to modify the app.
If the secret value is provided as a parameter in a URL query string with the name <secret-param>
then it is necessary to notify the ApproovService
that the query parameter is subject to substitution. You do this by making the call once, after initialization:
ApproovService.addSubstitutionQueryParam("<secret-param>")
After this the Approov interceptor should transform any instance of a URL such as https://mydomain.com/endpoint?<secret-param>=<secret-placeholder>
into https://mydomain.com/endpoint?<secret-param>=<secret-value>
, if the app passes attestation and there is a secure string with the name <secret-placeholder>
.
In order for Approov to recognize the app as being valid it needs to be registered with the service. Change the directory to the top level of your app project and then register the app with Approov:
approov registration -add app/build/outputs/apk/debug/app-debug.apk
Note, on Windows you need to substitute \ for / in the above command.
IMPORTANT: The registration takes up to 30 seconds to propagate across the Approov Cloud Infrastructure, therefore don't try to run the app again before this time has elapsed. During development of your app you can ensure it always passes on your device to not have to register the APK each time you modify it.
Managing Registrations provides more details for app registrations, especially for releases to the Play Store. Note that you may also need to apply specific Android Obfuscation rules for your app when releasing it.
If the app is not recognized as being valid by Approov then an ApproovRejectionException
is thrown on the request and the API call is not completed. The secret value will never be communicated to the app in this case.
Your app should specifically catch this exception and provide some feedback to the user to explain why the app is not working. The ApproovRejectionException
has a geARC()
method which provides an Attestation Response Code which can provide more information about the status of the device, without revealing any details to the end user.
If you wish to provide more direct feedback then enable the Rejection Reasons feature:
approov policy -setRejectionReasons on
Note that this command requires an admin role.
You will then be able to use getRejectionReasons()
on an ApproovRejectionException
to obtain a comma separated list of device properties responsible for causing the rejection.
See Getting Started With Approov for information about additional Approov features you may wish to try.
In some cases the value to be substituted on a header may be prefixed by some fixed string. A common case is the presence of Bearer
included in an authorization header to indicate the use of a bearer token. In this case you can specify a prefix as follows:
ApproovService.addSubstitutionHeader("Authorization", "Bearer ")
This causes the Bearer
prefix to be stripped before doing the lookup for the substitution, and the Bearer
prefix added to the actual secret value as part of the substitution.
As shown, it is possible to set predefined secret strings that are only communicated to passing apps. It is also possible to get and set secure string values for each app instance. These are never communicated to the Approov cloud service, but are encrypted at rest using keys which can only be retrieved by passing apps.
Here is an example of using the required method in ApproovService
:
import io.approov.service.retrofit.ApproovException
import io.approov.service.retrofit.ApproovRejectionException
import io.approov.service.retrofit.ApproovNetworkException
...
var key: String
var newDef: String?
var secret: String?
// define key and newDef here
try {
secret = ApproovService.fetchSecureString(key, newDef)
}
catch(e: ApproovRejectionException) {
// failure due to the attestation being rejected, e.getARC() and e.getRejectionReasons() may be used to present information to the user
// (note e.getRejectionReasons() is only available if the feature is enabled, otherwise it is always an empty string)
}
catch(e: ApproovNetworkException) {
// failure due to a potentially temporary networking issue, allow for a user initiated retry
}
catch(e: ApproovException) {
// a more permanent error, see e.getMessage()
}
// use `secret` as required, but never cache or store its value - note `secret` will be null if the provided key is not defined
to lookup a secure string with the given key
, returning null
if it is not defined. Note that you should never cache this value in your code. Approov does the caching for you in a secure way. You may define a new value for the key
by passing a new value in newDef
rather than null
. An empty string newDef
is used to delete the secure string.
This method is also useful for providing runtime secrets protection when the values are not passed on headers. Secure strings set using this method may also be looked up using subsequent networking interceptor header substitutions.
If you wish to reduce the latency associated with substituting the first secret, then make this call immediately after creating ApproovService
:
ApproovService.prefetch()
This initiates the process of fetching the required information as a background task, so that it is available immediately when subsequently needed. Note the information will automatically expire after approximately 5 minutes.
You may wish to do an early check in your to present a warning to the user if the app is not going to be able to access secrets because it fails the attestation process. Here is an example of calling the appropriate method in ApproovService
:
import io.approov.service.retrofit.ApproovException
import io.approov.service.retrofit.ApproovRejectionException
import io.approov.service.retrofit.ApproovNetworkException
...
try {
ApproovService.precheck()
}
catch(e: ApproovRejectionException) {
// failure due to the attestation being rejected, e.getARC() and e.getRejectionReasons() may be used to present information to the user
// (note e.getRejectionReasons() is only available if the feature is enabled, otherwise it is always an empty string)
}
catch(e: ApproovNetworkException) {
// failure due to a potentially temporary networking issue, allow for a user initiated retry
}
catch(e: ApproovException) {
// a more permanent error, see e.getMessage()
}
// app has passed the precheck
Note you should NEVER use this as the only form of protection in your app, this is simply to provide an early indication of failure to your users as a convenience. You must always also have secrets essential to the operation of your app, or access to backend API services, protected with Approov. This is because, although the Approov attestation itself is heavily secured, it may be possible for an attacker to bypass its result or prevent it being called at all. When the app is dependent on the secrets protected, it is not possible for them to be obtained at all without passing the attestation.