In a React Native app, why is a custom scheme link like resetapp://reset?token=… a poor carrier for a password-reset token, and what is scheme hijacking?
answer
- schemes are not owned
- another app can claim resetapp
- the token goes to whoever answers
- any page can call your scheme
- verified https links for secrets
basics
~20 sAny app can register the same custom scheme, and neither platform guarantees yours receives it, so a malicious app can intercept resetapp:// links and steal the token. Sensitive links belong on verified https domains, with short-lived tokens and validation.
solid answer
~50 sA custom scheme is a claim, not an ownership: any installed app can declare `resetapp` in its own `Info.plist` or intent filter. When two apps claim it, iOS does not guarantee which one opens, and on Android another app can appear alongside yours in a chooser or be picked by the user. That is **scheme hijacking**: the attacker's app receives `resetapp://reset?token=…` and with it the reset token. The reverse also holds — any web page or app can open your scheme with arbitrary parameters, so the reset screen must treat them as untrusted and never act without user confirmation. For secrets, use verified `https` links (Universal Links and App Links), whose domain association the OS checks, plus short-lived, single-use tokens bound to the account on the server, and a web fallback when the app is absent.
code
tsx · 30 linesimport * as React from 'react';
import { Button, Text, TextInput, View } from 'react-native';
import { submitPasswordReset } from './api';
const TOKEN_SHAPE = /^[A-Za-z0-9_-]{32,128}$/;
export function ResetPasswordScreen({ token }: { token: string | undefined }) {
const [password, setPassword] = React.useState('');
const [error, setError] = React.useState<string | null>(null);
if (!token || !TOKEN_SHAPE.test(token)) {
return <Text>This reset link is not valid. Request a new one.</Text>;
}
const onSubmit = async () => {
try {
await submitPasswordReset({ token, password });
} catch {
setError('This link has expired or was already used.');
}
};
return (
<View>
<TextInput secureTextEntry value={password} onChangeText={setPassword} />
{error ? <Text>{error}</Text> : null}
<Button title="Set new password" onPress={onSubmit} disabled={password.length === 0} />
</View>
);
}go deeper
Know that a custom scheme can be registered by any app, so links carrying secrets should not use one.
Explain hijacking as another app receiving your URLs, and why any page can also open your scheme with arbitrary parameters.
Move sensitive flows to verified https links, make tokens short-lived and single-use, and require explicit user action on the linked screen.
Set a policy for which entry points may use custom schemes and which must use verified links, including the support cost of each.
## The setup A password-reset email contains a button that opens the app on a "Choose a new password" screen. The tempting implementation is a custom scheme: `resetapp://reset-password?token=8f3c…`. The app registers `resetapp` in `Info.plist` and `AndroidManifest.xml`, the navigation library maps the path to the reset screen, and the token arrives as a param. It works in testing. The problem is who else it works for. ## Why a scheme is not owned Registering a custom scheme is a **declaration in your own app's manifest**. Nothing checks it against a registry, a domain or a signature: - Any app can declare `resetapp` in its own `CFBundleURLTypes` or intent filter. - On iOS, when more than one app claims a scheme, the system does not define which one opens it. - On Android, several apps can match the same `VIEW` intent; the user may see a chooser, and may pick the wrong one or have picked it before. **Scheme hijacking** is a malicious app registering your scheme to receive URLs meant for you. With a reset link, the URL *is* the secret: whoever receives `resetapp://reset-password?token=…` can complete the reset. ## The reverse direction: anyone can call you The same openness means any web page, email or app can open `resetapp://reset-password?token=anything` on a device where your app is installed: 1. The reset screen must treat `token` as **untrusted input** — validate its shape, then let the server decide. 2. The screen must not perform an action automatically on arrival. A link that silently changes settings, confirms a payment or links an account is a cross-app request forgery. 3. Error states must not leak information, such as whether an email address has an account. ## What to use instead | Property | Custom scheme `resetapp://` | Verified `https://` link | |---|---|---| | Who can claim it | any app | only the app associated with the domain | | App not installed | link fails | opens the website instead | | Works from desktop email | no | yes, as a normal web page | | Suitable for secrets | no | yes, with short-lived tokens | **Universal Links** (iOS) and **App Links** (Android) are `https` URLs whose domain publishes a file naming the app allowed to open them, and the OS verifies that association. Setting them up is a separate topic; the point here is that for anything carrying a secret, the scheme should be `https` on a domain you control. ## Defence in depth for the token Even on a verified link: - Make the token **single-use** and **short-lived**, and bind it to the account on the server. - Require the user to enter the new password on the screen — the link authorises showing the form, not changing anything by itself. - Let the server invalidate outstanding reset tokens once the password changes. ## When custom schemes are fine Custom schemes remain useful for low-risk entry points: opening the app's home screen from a widget, returning from a companion app, development and QA shortcuts, or links whose parameters are public identifiers. What they cannot do is prove that the app receiving the URL is yours. ## What a strong answer shows It explains that scheme registration is unverified, defines hijacking in terms of who receives the URL, notes the reverse risk of any page invoking the scheme, and moves secrets to verified `https` links with short-lived, single-use tokens and explicit user action on the screen.
- Does picking an obscure scheme name like com.example.resetapp prevent hijacking?It reduces accidental collisions but not deliberate ones. A malicious app can read your scheme from your binary or a captured link and declare the same string. Registration is unverified whatever the name, so secrets still need a verified `https` link.
- Why must the reset screen not change anything automatically when the link opens it?Any web page or app can open your scheme with parameters it chooses. If arrival alone triggered an action, an attacker could trigger it on the victim's device. The link should only open a form; the user's explicit submit, checked by the server, performs the change.
A custom scheme is a sign reading 'Deliveries for Reset Co. here' that anyone can hang on their door; a verified https link is a delivery to a registered address the courier checks first.
saying these in an interview costs you the question
- Only the app that registers a scheme first can ever receive its links.
- A long, unusual scheme name makes interception impossible.
- Links to your scheme can only come from your own emails and pages.
- Once the token arrives in the app it has been verified by the OS.
- Universal Links and App Links are just custom schemes that start with https.