Skip to main content

Command Palette

Search for a command to run...

Effortless Auto-Login in React Native: Using Device IDs to Keep Users Signed In

Published
4 min read
Effortless Auto-Login in React Native: Using Device IDs to Keep Users Signed In
O

I'm Ogunuyo Ogheneruemu Brown, a senior software developer. I specialize in DApp apps, fintech solutions, nursing web apps, fitness platforms, and e-commerce systems. Throughout my career, I've delivered successful projects, showcasing strong technical skills and problem-solving abilities. I create secure and user-friendly fintech innovations. Outside work, I enjoy coding, swimming, and playing football. I'm an avid reader and fitness enthusiast. Music inspires me. I'm committed to continuous growth and creating impactful software solutions. Let's connect and collaborate to make a lasting impact in software development.

Keeping users logged in across app restarts and backgrounding can be tricky when you rely on in-memory state alone. In this tutorial, we’ll build a device-based auto-login flow in a bare React Native app, backed by a simple PHP/MySQL service. When users first authenticate via OTP, we record their device’s unique ID in the database. On every subsequent cold start, the app checks that device ID—if it exists, we bypass login entirely and send them straight into the app.


📋 What You’ll Need

  1. A bare React Native project (not Expo-managed)

  2. react-native-device-info

  3. A MySQL database with two tables:

    • users (phone, email, fullname, …)

    • userdevice (phone, email, deviceId)

  4. PHP scripts for:

    • saving device records (savedevice.php)

    • checking device records (checkDevice.php)

    • deleting devices on logout (deleteDevice.php)


1. Read the Native Device ID

Install and link react-native-device-info:

npm install react-native-device-info
cd ios && pod install && cd ..

Create a reusable hook to fetch it:

// components/useDeviceId.js
import { useState, useEffect } from 'react';
import DeviceInfo from 'react-native-device-info';

export default function useDeviceId() {
  const [deviceId, setDeviceId] = useState(null);

  useEffect(() => {
    async function fetchId() {
      try {
        const id = await DeviceInfo.getUniqueId();
        setDeviceId(id);
      } catch (e) {
        console.warn('Failed to get deviceId', e);
      }
    }
    fetchId();
  }, []);

  return deviceId;
}

2. Record the Device on OTP Success

In your OTP verification screen, after confirming the code, send email, phone and deviceId to your backend:

// screens/OtpScreen.js (inside handleSubmit)
const handleSubmit = async () => {
  // …OTP validation…
  const deviceId = useDeviceId();
  await fetch('https://yourdomain.com/savedevice.php', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      email:        userEmail,
      mobileNumber: mobileNumber,
      deviceId,
    }),
  });
  navigation.navigate('Success');
};

PHP: savedevice.php

<?php
include("config.php");
$data     = json_decode(file_get_contents('php://input'), true);
$phone    = $data['mobileNumber'] ?? '';
$email    = $data['email']       ?? '';
$deviceId = $data['deviceId']    ?? '';

if (!$phone||!$email||!$deviceId) {
  echo json_encode(['status'=>'error','message'=>'Missing fields']);
  exit;
}

// Delete existing for this user or device
$stmt = $link->prepare("
  DELETE FROM userdevice
    WHERE phone = ? OR deviceId = ?
");
$stmt->bind_param("ss",$phone,$deviceId);
$stmt->execute();
$stmt->close();

// Insert the new record
$ins = $link->prepare("
  INSERT INTO userdevice (phone,email,deviceId)
    VALUES (?,?,?)
");
$ins->bind_param("sss",$phone,$email,$deviceId);
$ins->execute();
echo json_encode(['status'=>'success']);
$ins->close();

3. Auto-Login on App Cold Start

a) Build your Auth Context

// components/AuthContext.js
import React, { createContext, useState, useEffect } from 'react';
import useDeviceId from './useDeviceId';

export const AuthContext = createContext();

export function AuthProvider({ children }) {
  const deviceId = useDeviceId();
  const [isLoading,  setIsLoading]  = useState(true);
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const [user,       setUser]       = useState(null);

  useEffect(() => {
    if (!deviceId) return;
    fetch('https://yourdomain.com/checkDevice.php', {
      method: 'POST',
      headers: {'Content-Type':'application/json'},
      body: JSON.stringify({ deviceId }),
    })
    .then(r => r.json())
    .then(data => {
      if (data.status === 'success') {
        setUser(data.user);
        setIsLoggedIn(true);
      }
    })
    .finally(() => setIsLoading(false));
  }, [deviceId]);

  const logout = () => {
    fetch('https://yourdomain.com/deleteDevice.php', {
      method: 'POST',
      headers: {'Content-Type':'application/json'},
      body: JSON.stringify({ deviceId }),
    }).catch(console.warn);

    setIsLoggedIn(false);
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ isLoading, isLoggedIn, user, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

b) Backend: checkDevice.php

<?php
include("config.php");
$data     = json_decode(file_get_contents('php://input'), true);
$deviceId = $data['deviceId'] ?? '';
if (!$deviceId) exit;

$stmt = $link->prepare("
  SELECT u.phone,u.email,u.fullname
    FROM userdevice d
    JOIN users u ON u.phone=d.phone
   WHERE d.deviceId=?
   LIMIT 1
");
$stmt->bind_param("s",$deviceId);
$stmt->execute();
$stmt->bind_result($phone,$email,$fullname);
if ($stmt->fetch()) {
  echo json_encode([
    'status'=>'success',
    'user'=>compact('phone','email','fullname')
  ]);
} else {
  echo json_encode(['status'=>'not_found']);
}
$stmt->close();

c) Backend: deleteDevice.php

<?php
include("config.php");
$data     = json_decode(file_get_contents('php://input'), true);
$deviceId = $data['deviceId'] ?? '';
if (!$deviceId) exit;

$stmt = $link->prepare("DELETE FROM userdevice WHERE deviceId=?");
$stmt->bind_param("s",$deviceId);
$stmt->execute();
echo json_encode(['status'=>'success']);
$stmt->close();

4. Choose Your Navigation on Launch

In App.js, render either your login stack or your main drawer based on isLoggedIn:

// App.js
import React, { useContext } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { AuthProvider, AuthContext } from './components/AuthContext';
import AuthStack from './navigators/AuthStack';      // Login/OTP
import AppDrawer from './navigators/AppDrawer';      // Main app

function RootNavigator() {
  const { isLoading, isLoggedIn } = useContext(AuthContext);
  if (isLoading) return null;  // or <SplashScreen/>
  return isLoggedIn ? <AppDrawer/> : <AuthStack/>;
}

export default function App() {
  return (
    <AuthProvider>
      <NavigationContainer>
        <RootNavigator/>
      </NavigationContainer>
    </AuthProvider>
  );
}

5. Test on Simulator / Emulator

  1. First run → Login + OTP → device saved

  2. Kill the app

    • On iOS Simulator: ⌘+H, ⌘+⇧+H (twice), swipe away

    • Or xcrun simctl terminate booted <bundleId>

  3. Relaunch with npx react-native run-ios

  4. You should land directly in your app’s main drawer—no OTP needed.


Conclusion

By tying your authentication to a persistent device ID stored server-side, you give users a frictionless “open-and-you’re-in” experience, without sacrificing control (explicit logout still invalidates the device). This pattern works great for mobile apps where you trust the device boundary, and it keeps your React state clean and simple.

Feel free to adapt the server queries, error handling, and UI to your needs—happy coding!

More from this blog

iRuemu Coding Blog

108 posts