How to Test Social Login on React Native (Complete Guide)
How to Test Social Login on React Native (Complete Guide).
How to Test Social Login on React Native (Complete Guide).
Testing social login in a React Native application is more than verifying that a button opens a third‑party SDK; it is about ensuring that authentication flows survive real‑world conditions such as network interruptions, token expiration, conflicting native modules, and varied user behaviors. A missed edge case can leave users stranded on a login screen, expose private data, or trigger store‑policy violations. This guide walks you through why social login matters, how to construct a thorough test matrix, manual and automated approaches, React‑Native‑specific tooling, production‑only edge cases, accessibility and security checks, and how autonomous persona‑driven exploration uncovers bugs that scripted tests never see. By the end you will have a concrete checklist you can bookmark and apply to any React Native project that integrates Facebook, Google, Apple, Twitter, or any OAuth‑based provider.
Why Social Login Testing Matters in React Native
Social login shortcuts the registration funnel, but it introduces a surface area where native bridges, JavaScript threads, and external identity providers intersect. In React Native the JavaScript layer communicates with native modules via the React Native Bridge (or the newer TurboModules). If the bridge drops a message, the login promise may never resolve, leaving the UI stuck. Likewise, each provider supplies its own SDK (often a static library or a CocoaPods/Gradle dependency) that may clash with other native code, cause duplicate symbol errors, or trigger App Store rejections for misuse of privileged APIs.
From a product perspective, a broken social login can:
- Increase drop‑off rates because users abandon the flow after a second‑after‑seeing a spinner that never stops.
- Trigger security warnings if tokens are logged to console or stored insecurely.
- Cause privacy violations when the app inadvertently shares more data than the user consented to (e.g., requesting email scope without showing the consent screen).
- Lead to store rejection if the app attempts to access restricted APIs (like accessing the device's contacts via Facebook SDK without proper justification).
Because these failures often manifest only under specific device OS versions, provider SDK updates, or network conditions, a disciplined testing strategy is essential.
Building a Comprehensive Test Matrix
A test matrix captures the dimensions you need to exercise: happy path, error paths, edge cases, accessibility, and security/privacy. The table below organizes these dimensions across the most common providers (Facebook, Google, Apple, Twitter) and the React Native specifics that affect each cell.
| Test Dimension | Facebook Login | Google Sign‑In | Apple Sign‑In | Twitter / X Login | React‑Native Specific Checks |
|---|---|---|---|---|---|
| Happy Path | Successful token exchange, userinfo fetch, redirect to home | Same as Facebook, includes ID token | Same, includes JWT and authorization code | Same, includes OAuth token and secret | Bridge call success, native module init, JS promise resolution |
| Invalid Credentials | Provider returns error code 100, UI shows “Invalid email/password” | Same, error 400, shows “Invalid email” | Same, error 1000, shows “Apple ID not valid” | Same, error 32, shows “Bad authentication data” | Error handling in JS, proper UI feedback, no crash |
| Network Loss During Flow | Simulate offline after SDK init, expect graceful retry or timeout | Same, expect retry with exponential backoff | Same, expect fallback to device‑account login if available | Same, expect clear offline message | NetInfo listener, handling of pending promises, UI state reset |
| Token Expiry & Refresh | Expired access token, attempt silent refresh via SDK, expect new token | Same, use GoogleAuth.refresh() | Same, Apple does not provide refresh; re‑login required | Same, Twitter token long‑lived but can be revoked | Token storage (AsyncStorage/Keychain), refresh logic, UI update |
| SDK Version Mismatch | Use outdated Facebook SDK (e.g., 8.x) with latest RN, check for linker errors | Same, Google Play services version conflict | Same, Apple AuthenticationServices framework version | Same, Twitter SDK version | Podfile/Gradle resolution, duplicate symbol detection |
| Permission Scope Change | Request email, then later remove scope, expect consent screen again | Same, Google prompts for re‑consent | Same, Apple shows permission sheet again | Same, Twitter may show additional auth screen | Handling of multiple auth attempts, UI state reset |
| Deep Link / Universal Link | Provider redirects via custom scheme (myapp://auth/callback) | Same, uses Android App Links or iOS Universal Links | Same, uses ASWebAuthenticationSession callback URL | Same, uses custom scheme or universal link | Linking API correctness, handling of incoming URL in AppDelegate/MainActivity |
| Background / Foreground Switch | App sent to background during webview, return to foreground, expect continuation | Same, ensure webview not destroyed | Same, ensure authentication session not terminated | Same, ensure no leaked webview | AppState listeners, cleanup of listeners, preventing memory leaks |
| Accessibility (WCAG) | Button reachable via TalkBack/VoiceOver, label describes action, error messages announced | Same, ensure sufficient contrast, ARIA‑like labels via accessibilityLabel | Same, ensure button is not hidden behind other UI | Same, ensure login flow works with switch control | AccessibilityTest, audit with axe‑core/react‑native‑accessibility |
| Security / Privacy | No token logged to console, stored encrypted, minimal scopes requested | Same, ensure ID token not exposed in devtools | Same, ensure authorization code not leaked | Same, ensure OAuth token not shared with third‑party analytics | OWASP Mobile Top 10 checks, use of Keychain, disabling console.log in production |
Each cell represents a test scenario you should automate or at least verify manually. The matrix can be expanded with additional providers (LinkedIn, GitHub) or additional dimensions such as localization (right‑to‑left languages) or device‑specific hardware (tablet vs phone, notch vs no‑notch).
Manual Testing Workflow for Social Login
Manual testing remains valuable for exploratory checks, especially when you need to verify UI/UX nuances that automated scripts may overlook. Below is a step‑by‑step workflow you can follow for each provider.
1. Environment Preparation
- Install the latest Android Studio and Xcode.
- Create two device profiles: one with Google Play services (for Android) and one without (to test fallback behavior).
- On iOS, enable “Allow Untrusted Shortcuts” if testing Apple Sign‑In in a simulator that lacks a real Apple ID.
- Clear any existing credentials from the device’s account manager (Android Settings → Accounts) and the iOS Settings → Passwords & Accounts.
2. Baseline Happy Path
- Launch the app and navigate to the login screen.
- Tap the provider button (e.g., “Continue with Facebook”).
- Observe the native SDK UI (webview or modal) appear.
- Enter a valid test account credentials (use a dedicated test user for each provider).
- Complete any consent screens, granting only the scopes you declared.
- Verify that the app receives a token (you can log it temporarily in a dev build) and redirects to the intended screen (home, profile, etc.).
- Confirm that the UI shows the logged‑in state (user avatar, name, logout button).
3. Error Path Injection
- Invalid credentials – purposely mistype the password; ensure the provider’s error UI appears and the app shows a user‑friendly message (“Unable to sign in, please check your credentials”).
- Network loss – enable Airplane Mode after the SDK init but before the user taps “Continue”; the app should display a timeout or retry option, not crash.
- Token expiry – manually set the device clock forward (or use a tool like
adb shell dateon Android) to simulate an expired access token, then attempt a silent refresh; confirm the app either refreshes silently or prompts re‑login. - Permission change – after a successful login, go to the provider’s website or app and revoke a granted scope (e.g., remove email permission from Facebook). Return to the app and attempt an action that requires that scope; the app should detect the missing permission and re‑trigger the consent flow.
4. Deep Link and Linking Validation
- Use
adb shell am start -W -a android.intent.action.VIEW -d "myapp://auth/callback?token=abc"on Android to simulate the provider redirect. - On iOS, run
xcrun simctl openurl booted "myapp://auth/callback?token=abc". - Verify that the app parses the query parameters, extracts the token, and continues the flow without showing a blank screen.
5. Background/Foreground Switch
- Start the login flow, press the Home button (Android) or swipe up to the home screen (iOS) while the provider’s webview is visible.
- Wait 10‑15 seconds, then restore the app.
- Confirm that the login flow either resumes where it left off or cleanly restarts, without leaving a dangling webview that consumes memory.
6. Accessibility Check
- Enable TalkBack (Android) or VoiceOver (iOS).
- Navigate to the login button using swipe gestures; ensure the accessibility label announces the provider name and action (“Sign in with Google, button”).
- Trigger an error (invalid credentials) and verify that the error message is announced.
- Check color contrast using the device’s accessibility inspector or a tool like Google’s Accessibility Scanner.
7. Security/Privacy Spot Check
- Run the app with
adb logcat(Android) orConsole.app(iOS) and look for any accidental logging of tokens, authorization codes, or raw responses. - Inspect where tokens are stored: on Android, verify they are written to EncryptedSharedPreferences or Keystore; on iOS, verify they are placed in the Keychain with
kSecAttrAccessibleWhenUnlockedThisDeviceOnly. - Ensure that the requested scopes are the minimum necessary (e.g., avoid requesting
public_profileandemailtogether if only email is needed).
8. Cleanup and State Reset
- Log out of the app, then clear the app’s data (Settings → Apps → YourApp → Storage → Clear Data) or reinstall.
- Verify that no residual tokens remain in the device’s account manager or Keychain.
- Re‑run the happy path to ensure a fresh start works.
Following this manual workflow for each provider gives you confidence that the basic contract holds. However, manual testing is time‑consuming and prone to human error, which is why we layer automation on top.
Automated Testing Strategies for React Native Social Login
Automation can cover the repetitive happy‑path and error‑path checks, freeing exploratory time for edge cases. React Native’s hybrid nature means you need tools that can drive both the JavaScript layer and the native UI that providers present.
Choosing the Right Test Stack
- End‑to‑End (E2E) Frameworks – Detox (Android/iOS) and Appium (Android/iOS/web) are the most common. Detox synchronizes with the React Native run‑loop, making it ideal for testing pure RN screens. Appium drives the actual native UI, which is necessary when the provider SDK presents a webview or modal that lives outside the RN hierarchy.
- Unit/Integration Tests – Jest with @testing-library/react‑native validates that your login component dispatches the correct actions and handles promises correctly. Mock the native modules using
jest.mock(). - Contract Tests – Pact or custom mock servers can verify that the token exchange endpoint receives the expected JSON shape from the provider’s mock OAuth server.
Below we outline a combined strategy: Jest for logic, Detox for RN screens, and Appium for provider‑specific UI.
Setting Up Jest for Login Logic
# Install dependencies
npm i -D jest @testing-library/react-native @testing-library/jest-native
Create a mock for the Facebook SDK:
// __mocks__/react-native-fbsdk.js
export const LoginManager = {
logInWithPermissions: jest.fn().mockResolvedValue({ isCancelled: false }),
};
export const AccessToken = {
getCurrentAccessToken: jest.fn().mockResolvedValue({ accessToken: 'token123' }),
};
Test the login component:
// Login.test.js
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import LoginButton from '../components/LoginButton';
import * as FBSDK from 'react-native-fbsdk';
test('logs in successfully and calls onLogin', async () => {
const onLogin = jest.fn();
const { getByLabelText } = render(<LoginButton onLogin={onLogin} />);
await fireEvent.press(getByLabelText('Sign in with Facebook'));
expect(FBSDK.LoginManager.logInWithPermissions).toHaveBeenCalledWith(
expect.arrayContaining(['email'])
);
await waitFor(() => expect(onLogin).toHaveBeenCalledWith('token123'));
});
Run with npm test. This validates that your component correctly invokes the SDK and passes the token upward.
Detox for Pure RN Screens
Detox excels at testing navigation after the token is received, assuming you have mocked the native module to return a deterministic token.
npm i -D detox
detox init -r jest
Configure detox.config.js for Android emulator:
module.exports = {
testRunner: jest,
apps: {
android.debug: {
binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
},
},
devices: {
simulator: {
type: 'android.emulator',
device: {
avdName: 'Pixel_4_API_33',
},
},
},
configurations: {
'android.debug': {
device: 'simulator',
app: 'android.debug',
},
},
};
Write a test that fakes the SDK response using Detox’s ability to mock native modules via device.sendToApp:
// e2e/login.test.js
describe('Social Login Flow', () => {
beforeEach(async () => {
await device.reloadApp();
// Mock the native module to return a fixed token
await device.sendToApp({ type: 'MOCK_FACEBOOK_LOGIN', token: 'faketoken123' });
});
it('should navigate to home after successful login', async () => {
await element(by.id('facebook-login-button')).tap();
await expect(element(by.id('home-screen'))).toBeVisible();
await expect(element(by.text('Welcome, testuser'))).toBeVisible();
});
});
In your React Native code, add a listener for the custom event:
import { NativeEventEmitter, NativeModules } from 'react-native';
const { RNLoginMock } = NativeModules;
const loginEmitter = new NativeEventEmitter(RNLoginMock);
loginEmitter.addListener('MOCK_FACEBOOK_LOGIN', ({ token }) => {
// Replace the real SDK call with a resolved promise
LoginManager.logInWithPermissions = () => Promise.resolve({ isCancelled: false, token });
});
Detox tests run fast because they avoid the actual provider UI, letting you verify navigation, state management, and error handling at scale.
Appium for Provider‑Specific UI
When you need to assert that the actual Google Sign‑In webview appears, or that the Apple authentication sheet is presented, Appium is the right choice. It drives the real native UI, so you can validate webview elements, handle alerts, and test deep link callbacks.
#### Install Appium
npm i -g appium
npm i -D appium webdriverio @wdio/cli
Create a basic WDIO config (wdio.conf.js) targeting Android:
exports.config = {
runner: 'local',
specs: ['./test/specs/**/*.js'],
capabilities: [{
platformName: 'Android',
'appium:automationName': 'UiAutomator2',
'appium:app': path.resolve(__dirname, '../android/app/build/outputs/apk/debug/app-debug.apk'),
'appium:deviceName': 'Pixel_4_API_33_emulator',
'appium:platformVersion': '13.0',
'appium:noReset': true,
}],
logLevel: 'info',
};
Write a test that initiates Google sign‑in and validates the webview:
// test/specs/google-login.spec.js
describe('Google Sign‑In via Appium', () => {
it('should show Google webview and allow login', async () => {
const loginBtn = await $('~google-login-button'); // accessibility id
await loginBtn.click();
// Switch to webview context (Chrome)
const contexts = await driver.getContexts();
const webviewContext = contexts.find(c => c.includes('WEBVIEW'));
await driver.switchContext(webviewContext);
// Wait for email field
const emailInput = await $('#identifierId');
await emailInput.waitForExist({ timeout: 10000 });
await emailInput.setValue('testuser@gmail.com');
await $('#identifierNext').click();
// Wait for password field
const passInput = await $('#password input[type="password"]');
await passInput.waitForExist({ timeout: 10000 });
await passInput.setValue('SecurePass!123');
await $('#passwordNext').click();
// Expect to return to native context after success
await driver.switchContext('NATIVE_APP');
const homeLabel = await $('~home-screen-label');
await homeLabel.waitForExist({ timeout: 15000 });
expect(await homeLabel.getText()).toEqual('Home');
});
});
Run with npx wdio run wdio.conf.js. This test validates that the app correctly hands off control to Google’s OAuth UI, that the user can supply credentials, and that control returns to the app with a successful state.
Combining the Layers
A robust CI pipeline might look like:
- Unit/Jest – runs on every push, validates logic and mocks.
- Detox – runs on nightly builds, exercises navigation and state with mocked SDKs.
- Appium – runs on scheduled releases (e.g., nightly or pre‑release) to catch provider UI changes.
- Manual exploratory – performed before major releases or after SDK updates.
This layered approach gives you fast feedback while still covering the parts that are hardest to mock.
Tooling and Libraries for React Native Social Login Testing
Beyond the core test frameworks, several libraries simplify mocking, token handling, and debugging.
| Category | Library / Tool | Purpose | Example Usage |
|---|---|---|---|
| SDK Mocking | react-native-fbsdk-mock, react-native-google-signin-mock | Provides jest‑friendly mocks that return deterministic tokens | jest.mock('react-native-fbsdk', () => require('react-native-fbsdk-mock')); |
| Secure Storage | @react-native-async-storage/async-storage (dev) / @react-native-keychain/keychain (prod) | Swap storage implementations for testing | In jest setup: jest.mock('@react-native-keychain/keychain', () => ({ setGenericPassword: jest.fn(), getGenericPassword: jest.fn().mockResolvedValue({ username: 'test', password: 'tok' }) })); |
| Deep Link Handling | react-native-linking (built‑in) + linking-test | Simulate inbound URLs in unit tests | Linking.addListener('url', ({ url }) => { /* handle */ }); await Linking.openURL('myapp://auth/callback?token=abc'); |
| Network Interception | msw (Mock Service Worker) + react-native-mock-service-worker | Intercept fetch/XMLHttpRequest to simulate token endpoints | const server = setupServer(rest.get('https://api.example.com/me', (req, res, ctx) => res(ctx.json({ id: '1', email: 'test@example.com' })))); |
| Accessibility Audit | @axe-core/react-native | Run automated WCAG checks in CI | await axe.run(); |
| Performance / Memory | flipper-plugin-react-native-performance | Detect leaks caused by abandoned webviews | Use Flipper UI to monitor JavaScript heap while toggling login flow. |
| Cloud Device Farms | Firebase Test Lab, AWS Device Headless | Run Appium/Detox on real hardware matrices | gcloud firebase test android run --app app-debug.apk --test appium-test.apk --device model=Pixel4,version=33 |
Practical Example: Mocking the Apple Authentication Services
Apple’s AuthenticationServices framework does not have a pure‑JS counterpart, but you can mock the native module that bridges to it.
// __mocks__/@react-native-apple-authentication/apple-authentication.js
export default {
performRequest: jest.fn().mockResolvedValue({
user: {
name: { firstName: 'John', lastName: 'Doe' },
email: 'john.doe@example.com',
realUserStatus: 1, // REALUSER
},
credential: {
authorizationCode: 'authcode123',
identityToken: 'idtoken.jwt',
},
}),
getCredentialStateForUser: jest.fn().mockResolvedValue({ state: 0 }), // UNKNOWN
};
Then in your test:
import AppleAuth from '@react-native-apple-authentication/apple-authentication';
test('Apple login returns user info', async () => {
const creds = await AppleAuth.performRequest({
requestedOperation: AppleAuth.Operation.LOGIN,
requestedScopes: [AppleAuth.Scope.EMAIL, AppleAuth.Scope.FULL_NAME],
});
expect(creds.user.email).toBe('john.doe@example.com');
});
This approach lets you verify that your login component correctly processes the Apple credential shape without needing a real Apple ID on the CI machine.
Edge Cases That Only Appear in Production
Even with exhaustive unit and E2E tests, certain failure modes surface only when the app runs in the wild. Below are common production‑only social login pitfalls and how to detect or mitigate them.
1. Provider SDK Updates Breaking Native Linking
When Facebook releases a new SDK version that drops support for older AndroidX libraries, you may see a build‑time error like Duplicate class com.facebook.internal.AttributionIdentifiers. This error only appears after you bump the SDK version in build.gradle or Podfile.
Mitigation:
- Enable
strictVersionMatcher=truein Gradle resolutionStrategy. - Use
react-native-fbsdk’sversionfield inpackage.jsonto lock to a known‑good range. - Add a CI step that runs
./gradlew :app:dependenciesand fails if any duplicate classes are reported.
2. Silent Token Refresh Loops
Some providers issue short‑lived access tokens (e.g., 1 hour) and expect the client to silently refresh using a refresh token. If your refresh logic mistakenly treats a 401 as a fatal error and triggers another login attempt, you can get an infinite loop of webviews, draining battery and frustrating users.
Detection:
- Instrument your auth service with a counter that logs each refresh attempt.
- In staging, set the device clock to simulate token expiry and observe the log.
- Assert that the refresh count never exceeds a threshold (e.g., 3) within a short window.
3. Webview Hijacking by Malicious Intent Filters
On Android, a rogue app can register an intent filter for your custom scheme (myapp://) and intercept the OAuth redirect, stealing the authorization code. This is only exploitable if you use a custom scheme and do not verify the redirect URI’s host.
Mitigation:
- Switch to Android App Links (HTTP(S) URLs) with domain verification.
- In the
onNewIntenthandler, validate that the incoming URL’s host matches your registered domain (myapp.com). - Add a runtime check: if the scheme is custom, show a warning in debug builds.
4. iOS ASWebAuthenticationSession Presentation Style Changes
Starting iOS 13, Apple introduced ASWebAuthenticationSession with a preference for presentationContextProvider. If you forget to set this provider, the session may present modally on iPad but full‑screen on iPhone, leading to layout issues or the session being dismissed by the system when the app goes to background.
Detection:
- Run the app on both iPad and iPhone simulators, trigger Apple login, then background the app.
- Verify that the session persists and returns a callback.
- Use the
presentationContextProviderto return aUIViewControllerthat is the root view controller of your window.
5. Token Leakage via Android Logcat
If you inadvertently console.log the raw response from the OAuth endpoint, the token becomes visible in logcat, which any other app with READ_LOGS permission can read (though restricted on newer Android versions, still a risk in debug builds).
Mitigation:
- Strip
console.logcalls in production usingbabel-plugin-transform-remove-console. - Use a custom logger that respects a
LEVELenv var (errorin production). - Run a CI grep step:
grep -r "token" src/ --exclude-dir=node_modulesand fail if any matches appear outside of test files.
6. Consent Screen Locale Mismatch
When testing with a device set to a right‑to‑left language (e.g., Arabic), some providers’ consent screens do not mirror correctly, causing buttons to be off‑screen or overlapped. This only appears when the device locale differs from your development language.
Mitigation:
- Include locale‑specific UI tests in your matrix (see the accessibility table).
- Use the
react-native-i18nlibrary to force a locale in test scripts and verify layout withreact-native-testing-library’stoMatchImageSnapshot(with Jest image snapshot plugin).
7. Background Location Permissions Triggered by SDK
Certain social SDKs (e.g., Facebook) may request location permissions if you enable features like “Nearby Friends”. If your app does not declare ACCESS_FINE_LOCATION in the manifest but the SDK triggers the prompt, the user sees a confusing permission request that can lead to abandonment.
Detection:
- Run the app with the Facebook SDK initialized but without calling any location‑related API.
- Monitor
adb logcatforPermissionRequestmessages. - Ensure you only initialize the SDK with the minimal feature set you need (e.g., avoid importing
FacebookAdsif not used).
8. Network Proxy Interception in Enterprise Environments
Corporate Wi‑Fi often uses SSL‑inspecting proxies that rewrite TLS certificates. If your app pins the provider’s certificate (a good security practice), the connection will fail with SSLHandshakeException. Users on such networks will see a generic “Unable to connect” message.
Mitigation:
- Offer a fallback to system trust store when certificate pinning fails (only in non‑sensitive flows).
- Detect the error and display a helpful message: “Please disable SSL inspection or try a different network.”
- Log the error internally (without exposing the pin) for internal diagnostics.
By adding these production‑focused checks to your test matrix—either as automated assertions (e.g., duplicate class detection) or as manual exploratory steps (e.g., locale switching)—you reduce the chance of unpleasant surprises after release.
Accessibility and Security Considerations
Social login touches two critical quality dimensions: accessibility (ensuring all users can authenticate) and security/privacy (protecting tokens and user data). Below we detail concrete checks you can embed in your test matrix.
Accessibility Checklist (WCAG 2.1 AA)
| Criterion | Test Method | Expected Result |
|---|---|---|
| Labeling | Inspect accessibilityLabel of each provider button via getAccessibilityInfo (Android) or AXUIElementCopyAttributeValue (iOS). | Label must describe the action (“Sign in with Google”). |
| Contrast | Use a contrast‑checking tool (e.g., react-native-accessibility-checker) on button backgrounds and text. | Minimum 4.5:1 for normal text, 3:1 for large text. |
| Touch Target Size | Measure button dimensions; ensure ≥48 dp (Android) or ≥44 pt (iOS). | Pass. |
| Screen Reader Navigation | Enable TalkBack/VoiceOver, swipe to login button, double‑tap to activate. | Focus moves to button, activation triggers login flow. |
| Error Announcement | Trigger an invalid login, verify that the error message is spoken. | Error message announced promptly. |
| Reduced Motion | Reduce animation scale in device settings, verify login UI does not rely on non‑essential motion. | UI still functional, no missing content. |
| Dynamic Type | Set largest font size, verify button text scales and does not overflow. | Text scales, layout adapts. |
| Closed Captions (if provider shows video) | Not applicable for most social login, but ensure any instructional video has captions. | N/A. |
Automate as much as possible with jest + @testing-library/react-native + axe-core/react-native. Example:
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('login screen passes axe accessibility checks', async () => {
const { container } = render(<LoginScreen />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
Security & Privacy Checklist
| Area | Test | Tool / Method |
|---|---|---|
| Token Storage | Verify token is stored in Keychain (iOS) or EncryptedSharedPreferences/Keystore (Android). | Use react-native-keychain getAllCredentials and assert that returned value is encrypted (not plaintext). |
| Logging | Ensure no console.log or console.warn outputs contain accessToken, idToken, authorizationCode. | grep -r "accessToken" src/ in CI; use react-native-logger with level filtering. |
| Network Sniffing | Run the app on a device hooked to Charles Proxy or mitmproxy; confirm TLS handshake succeeds and that no clear‑text token appears in request/response bodies. | Proxy logs. |
| Scope Minimization | Confirm that the requested scopes match the minimal set required for post‑login features. | Compare authRequest.scopes vs. requiredScopes in code. |
| Certificate Pinning | Validate that the app rejects connections when presented with a fake certificate (use mitmproxy with a custom CA). | Expect SSLHandshakeException. |
| JWT Validation (if you decode ID token client‑side) | Ensure you verify signature, expiry, audience, and issuer before trusting claims. | Use jwt-decode + jose library; unit test with tampered token. |
| Session Fixation | After login, attempt to reuse an old session token from a previous device; server should reject. | Backend test or mock server. |
| Privacy Policy Link | Verify that the login screen includes a link to your privacy policy and that tapping it opens the correct URL. | expect(linkingURL).toBe('https://example.com/privacy'). |
Implementing these checks as unit or integration tests gives you early feedback. For example, a test that asserts token storage encryption:
import * as Keychain from 'react-native-keychain';
import { storeToken
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free