Secure mobile identity without exposed keys.
Mobile apps must never bundle secret API keys within their source code, as binary files (.apk or .ipa) are easily decompiled. Utilize the Backend Proxy Pattern to keep your application unhackable.
React Native / Flutter
| Backend Proxy Pattern
proxy-server.js
// 1. Intercept mobile requests securely on your proxy backend
app.post('/api/mobile/send-otp', async (req, res) => {
const response = await fetch('https://api.authepy.com/api/...', {
headers: { 'Authorization': `Bearer $` }
});
});
// 2. Never ship your Secret Keys into the IPA / APK
✓ Binary decompilation safely mitigated
01
The Backend Proxy
Your mobile app must communicate with your own lightweight server (Node.js, Go, Python), not directly with Authepy. Store your Standard Secret Key strictly on this server to intercept and route requests securely.
proxy-server.js Node.js Middleware
const express = require('express');
const app = express();
app.use(express.json());
const AUTHEPY_API = 'https://api.authepy.com/api';
app.post('/api/mobile/send-otp', async (req, res) => {
const { email } = req.body;
try {
const authRes = await fetch(`${AUTHEPY_API}/otp/request`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.AUTHEPY_SECRET_KEY}`
},
body: JSON.stringify({ email })
});
const data = await authRes.json();
if (!data.success) return res.status(400).json({ error: data.error });
return res.status(200).json({ success: true, requestId: data.requestId });
} catch (error) {
return res.status(500).json({ error: "Internal dispatch error." });
}
});
app.post('/api/mobile/verify-otp', async (req, res) => {
const { requestId, code } = req.body;
try {
const authRes = await fetch(`${AUTHEPY_API}/otp/verify`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.AUTHEPY_SECRET_KEY}`
},
body: JSON.stringify({ requestId, userGuess: code })
});
const data = await authRes.json();
if (!data.success) return res.status(400).json({ error: data.error });
// Success! Generate a JWT to send back to the mobile app
const mobileSessionToken = "generate_your_jwt_here";
return res.status(200).json({ success: true, token: mobileSessionToken });
} catch (error) {
return res.status(500).json({ error: "Internal verification error." });
}
}); 02
The Mobile Client
Communicate with your new proxy from the mobile app. Crucially, utilize textContentType="oneTimeCode" on iOS and Android to trigger native SMS auto-fill above the keyboard.
MobileLoginScreen.js React Native
import React, { useState } from 'react';
import { View, TextInput, Button, Text, Alert } from 'react-native';
import * as Keychain from 'react-native-keychain';
export default function MobileLoginScreen() {
const [email, setEmail] = useState('');
const [code, setCode] = useState('');
const [requestId, setRequestId] = useState(null);
const [step, setStep] = useState('request');
// Point to YOUR proxy server, not Authepy directly
const YOUR_PROXY_URL = 'https://api.yourbackend.com/mobile';
const handleRequestOTP = async () => {
try {
const res = await fetch(`${YOUR_PROXY_URL}/send-otp`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email })
});
const data = await res.json();
if (data.success) {
setRequestId(data.requestId);
setStep('verify');
} else {
Alert.alert("Error", data.error);
}
} catch (err) { Alert.alert("Error", "Could not reach server."); }
};
const handleVerifyOTP = async () => {
try {
const res = await fetch(`${YOUR_PROXY_URL}/verify-otp`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ requestId, code })
});
const data = await res.json();
if (data.success && data.token) {
// Secure hardware storage instead of AsyncStorage
await Keychain.setGenericPassword('session', data.token);
Alert.alert("Success", "Logged in securely!");
} else {
Alert.alert("Invalid Code", data.error);
}
} catch (err) { Alert.alert("Error", "Verification failed."); }
};
return (
<View style={{ padding: 20 }}>
{step === 'request' ? (
<>
<TextInput placeholder="name@company.com" value={email} onChangeText={setEmail} keyboardType="email-address" autoCapitalize="none" />
<Button title="Send Login Code" onPress={handleRequestOTP} />
</>
) : (
<>
<Text>Enter the 6-digit code</Text>
<TextInput placeholder="000000" value={code} onChangeText={setCode} keyboardType="number-pad" maxLength={6} textContentType="oneTimeCode" />
<Button title="Verify Code" onPress={handleVerifyOTP} />
</>
)}
</View>
);
} Ready to deploy?
Don't risk your App Store approval with exposed keys. Grab a Standard Key and build your proxy in minutes.