Persistent login allows a user to remain signed in after closing and reopening the React Native application. Instead of asking the user to enter their email and password every time, the application can restore the authentication state using a stored authentication credential.
Persistent login means restoring a user's authenticated state when the application starts again.
First Login
↓
JWT Token
↓
Store Token
↓
Close App
↓
Open App
↓
Read Token
↓
Validate Session
↓
User Remains Logged In
| Normal Login | Persistent Login |
|---|---|
| User logs in | User logs in |
| Token stored | Token stored |
| App closes | App closes |
| User may need login again | App restores authentication |
import AsyncStorage
from "@react-native-async-storage/async-storage";
await AsyncStorage.setItem(
"authToken",
token
);
The token can later be read when the application starts.
const token =
await AsyncStorage.getItem(
"authToken"
);
If the key does not exist, the result is null.
const token: string | null =
await AsyncStorage.getItem(
"authToken"
);
TypeScript correctly represents the possibility that no stored token exists.
if (token) {
console.log(
"Stored authentication token found"
);
} else {
console.log(
"No stored token"
);
}
const [loading, setLoading] =
useState(true);
The application should remain in a loading state while it checks whether a previous authentication session can be restored.
useEffect(() => {
const restoreAuth =
async () => {
const token =
await AsyncStorage.getItem(
"authToken"
);
if (token) {
setToken(token);
}
setLoading(false);
};
restoreAuth();
}, []);
The authentication restoration process should run when the application or authentication provider starts.
useEffect(() => {
restoreAuth();
}, []);
The empty dependency array means the effect runs after the component is initially mounted.
A token may identify the user, but the application often needs current profile information as well.
const response =
await api.get<UserResponse>(
"/profile.php"
);
setUser(
response.data.user
);
const token =
await AsyncStorage.getItem(
"authToken"
);
if (token) {
setToken(token);
const response =
await api.get<UserResponse>(
"/profile.php"
);
setUser(
response.data.user
);
}
Finding a token in local storage does not prove that the token is still valid. The protected API should verify it.
Stored Token
↓
Protected API Request
↓
PHP JWT Verification
↓
Valid?
↙ ↘
Yes No
↓ ↓
User Logout
try {
const response =
await api.get(
"/profile.php"
);
} catch (error) {
if (
axios.isAxiosError(error) &&
error.response?.status === 401
) {
await AsyncStorage.removeItem(
"authToken"
);
}
}
await AsyncStorage.removeItem(
"authToken"
);
setToken(null);
setUser(null);
This returns the application to an unauthenticated state.
interface AuthContextType {
user: User | null;
token: string | null;
isAuthenticated: boolean;
loading: boolean;
login: (
token: string,
user: User
) => Promise<void>;
logout: () => Promise<void>;
}
Auth Context is a convenient place to manage persistent authentication.
const RootNavigator = () => {
const {
loading,
isAuthenticated
} = useAuth();
if (loading) {
return <LoadingScreen />;
}
return isAuthenticated
? <AppNavigator />
: <AuthNavigator />;
};
The loading state prevents the wrong navigator from appearing while authentication is being restored.
api.interceptors.request.use(
async config => {
const token =
await AsyncStorage.getItem(
"authToken"
);
if (token) {
config.headers.Authorization =
`Bearer ${token}`;
}
return config;
}
);
The interceptor can automatically use the restored token for protected API requests.
App Opens
↓
AuthProvider Starts
↓
Read AsyncStorage
↓
Token Found?
↙ ↘
Yes No
↓ ↓
Call Login
Profile Screen
API
↓
Verify JWT
↓
User Found
↓
App Navigator
const login = async (
newToken: string,
newUser: User
) => {
await AsyncStorage.setItem(
"authToken",
newToken
);
setToken(newToken);
setUser(newUser);
};
Saving the token during login allows it to be restored later.
const logout = async () => {
await AsyncStorage.removeItem(
"authToken"
);
setToken(null);
setUser(null);
};
Once the token is removed, the next application start will not restore the previous login from that token.
const restoreAuth =
async () => {
try {
const savedToken =
await AsyncStorage.getItem(
"authToken"
);
if (!savedToken) {
return;
}
const response =
await api.get<UserResponse>(
"/profile.php"
);
setToken(savedToken);
setUser(
response.data.user
);
} catch (error) {
await AsyncStorage.removeItem(
"authToken"
);
setToken(null);
setUser(null);
} finally {
setLoading(false);
}
};
useEffect(() => {
restoreAuth();
}, []);
This can be placed inside the AuthProvider so authentication restoration happens when the provider is initialized.
useEffect(() => {
const restoreAuth =
async () => {
try {
const token =
await AsyncStorage.getItem(
"authToken"
);
if (!token) {
return;
}
const response =
await api.get<UserResponse>(
"/profile.php"
);
setToken(token);
setUser(
response.data.user
);
} catch {
await AsyncStorage.removeItem(
"authToken"
);
setToken(null);
setUser(null);
} finally {
setLoading(false);
}
};
restoreAuth();
}, []);
App Starts
↓
AsyncStorage
↓
JWT Found
↓
Axios
↓
Request Interceptor
↓
Authorization: Bearer JWT
↓
PHP Profile API
↓
JWT Verification
↓
User Response
↓
Auth Context
↓
App Navigator
try {
const response =
await api.get(
"/profile.php"
);
} catch (error) {
if (
axios.isAxiosError(error) &&
!error.response
) {
console.log(
"Network error"
);
}
}
A network failure is different from a server response such as HTTP 401. The application should handle both cases appropriately.
src/
│
├── context/
│ └── AuthContext.tsx
│
├── navigation/
│ ├── RootNavigator.tsx
│ ├── AuthNavigator.tsx
│ └── AppNavigator.tsx
│
├── services/
│ └── api.ts
│
├── screens/
│ ├── LoginScreen.tsx
│ ├── HomeScreen.tsx
│ └── ProfileScreen.tsx
│
└── types/
└── auth.ts
First Login
↓
PHP Login API
↓
JWT Token
↓
AsyncStorage
↓
Close Application
↓
Open Application
↓
AuthProvider
↓
Read Stored Token
↓
Axios Protected Request
↓
PHP JWT Verification
↓
Profile API
↓
User Data
↓
Auth Context
↓
isAuthenticated = true
↓
App Navigator
Persistent login provides a smooth mobile experience while still allowing the server to validate the authentication credential whenever the application restores the session.
Question: What is the main purpose of persistent login in React Native?