Redux can be used to manage authentication state centrally in a React Native application. Instead of keeping authentication information only inside individual screens, Redux can store the current user, token, loading state, and authentication status in one predictable state store.
Redux authentication means keeping login-related application state inside a Redux store.
Login
↓
Redux Store
↓
User + Token + Auth Status
↓
Protected Screens
↓
Axios
↓
PHP REST API
interface User {
id: number;
name: string;
email: string;
role: "admin" | "teacher" | "student";
}
interface AuthState {
user: User | null;
token: string | null;
isAuthenticated: boolean;
loading: boolean;
}
Redux Toolkit is the recommended approach for writing Redux logic. It reduces the amount of boilerplate code required for actions, reducers, and store configuration.
import {
createSlice
} from "@reduxjs/toolkit";
const initialState: AuthState = {
user: null,
token: null,
isAuthenticated: false,
loading: true
};
The initial state represents an application that is still checking whether an existing authentication session can be restored.
const authSlice =
createSlice({
name: "auth",
initialState,
reducers: {
}
});
The auth slice will contain authentication-related state and reducers.
login: (
state,
action
) => {
state.user =
action.payload.user;
state.token =
action.payload.token;
state.isAuthenticated =
true;
state.loading =
false;
}
logout: state => {
state.user = null;
state.token = null;
state.isAuthenticated = false;
state.loading = false;
}
The logout reducer resets the authentication state in Redux.
interface LoginPayload {
token: string;
user: User;
}
The login action can receive both the JWT token and authenticated user information.
export const {
login,
logout
} = authSlice.actions;
These actions can be dispatched from React Native screens or other application logic.
export default
authSlice.reducer;
The reducer is later added to the Redux store.
import {
configureStore
} from "@reduxjs/toolkit";
import authReducer
from "./authSlice";
export const store =
configureStore({
reducer: {
auth: authReducer
}
});
import {
Provider
} from "react-redux";
import {
store
} from "./store";
const App = () => {
return (
<Provider store={store}>
<AppNavigator />
</Provider>
);
};
The Redux Provider makes the store available to the application.
import {
useSelector
} from "react-redux";
const user =
useSelector(
state => state.auth.user
);
useSelector reads data from the Redux store.
export type RootState =
ReturnType<
typeof store.getState
>;
This allows TypeScript to understand the structure of the Redux store.
const user =
useSelector(
(state: RootState) =>
state.auth.user
);
TypeScript can now provide type checking when accessing authentication state.
import {
useDispatch
} from "react-redux";
const dispatch =
useDispatch();
useDispatch is used to send actions to the Redux store.
dispatch(
login({
token: response.data.token,
user: response.data.user
})
);
After a successful login API response, the token and user can be stored in Redux state.
dispatch(
logout()
);
The logout action clears the authentication state in Redux.
const LoginScreen = () => {
const dispatch =
useDispatch();
const handleLogin =
async () => {
const response =
await api.post(
"/login.php",
{
email,
password
}
);
dispatch(
login({
token:
response.data.token,
user:
response.data.user
})
);
};
return (
<Button
title="Login"
onPress={handleLogin}
/>
);
};
Redux state exists in memory. If the application is closed completely, the Redux state normally disappears. AsyncStorage or another persistence mechanism can be used to restore authentication information.
Login
↓
Redux State
+
AsyncStorage
↓
App Restart
↓
Read Stored Token
↓
Restore Redux State
const restoreAuth =
async () => {
const token =
await AsyncStorage.getItem(
"authToken"
);
if (!token) {
return;
}
try {
const response =
await api.get<UserResponse>(
"/profile.php"
);
dispatch(
login({
token,
user:
response.data.user
})
);
} catch {
await AsyncStorage.removeItem(
"authToken"
);
dispatch(logout());
}
};
api.interceptors.request.use(
async config => {
const token =
await AsyncStorage.getItem(
"authToken"
);
if (token) {
config.headers.Authorization =
`Bearer ${token}`;
}
return config;
}
);
Axios can read the stored token while Redux manages the application's authentication state.
const RootNavigator = () => {
const isAuthenticated =
useSelector(
(state: RootState) =>
state.auth.isAuthenticated
);
const loading =
useSelector(
(state: RootState) =>
state.auth.loading
);
if (loading) {
return <LoadingScreen />;
}
return isAuthenticated
? <AppNavigator />
: <AuthNavigator />;
};
import {
createSlice,
PayloadAction
} from "@reduxjs/toolkit";
interface User {
id: number;
name: string;
email: string;
role: "admin" | "teacher" | "student";
}
interface AuthState {
user: User | null;
token: string | null;
isAuthenticated: boolean;
loading: boolean;
}
interface LoginPayload {
token: string;
user: User;
}
const initialState: AuthState = {
user: null,
token: null,
isAuthenticated: false,
loading: true
};
const authSlice =
createSlice({
name: "auth",
initialState,
reducers: {
login: (
state,
action: PayloadAction<LoginPayload>
) => {
state.user =
action.payload.user;
state.token =
action.payload.token;
state.isAuthenticated =
true;
state.loading =
false;
},
logout: state => {
state.user = null;
state.token = null;
state.isAuthenticated =
false;
state.loading =
false;
},
setLoading: (
state,
action: PayloadAction<boolean>
) => {
state.loading =
action.payload;
}
}
});
export const {
login,
logout,
setLoading
} = authSlice.actions;
export default
authSlice.reducer;
import {
configureStore
} from "@reduxjs/toolkit";
import authReducer
from "./authSlice";
export const store =
configureStore({
reducer: {
auth: authReducer
}
});
export type RootState =
ReturnType<
typeof store.getState
>;
export type AppDispatch =
typeof store.dispatch;
import {
useDispatch,
useSelector
} from "react-redux";
import type {
RootState,
AppDispatch
} from "./store";
export const useAppDispatch =
() => useDispatch<AppDispatch>();
export const useAppSelector =
useSelector.withTypes<RootState>();
Typed hooks reduce repeated TypeScript annotations when working with Redux.
const handleLogout = async () => {
await AsyncStorage.removeItem(
"authToken"
);
dispatch(
logout()
);
};
The local credential and Redux authentication state should be cleared together.
Login Screen
↓
PHP Login API
↓
JWT + User
↓
Redux Auth Slice
↓
┌───────────────┐
│ User │
│ Token │
│ Auth Status │
└───────────────┘
↓
AsyncStorage
↓
Axios
↓
Bearer JWT
↓
Protected PHP API
↓
JWT Verification
Redux handles the client-side authentication state while the PHP API remains responsible for authentication and authorization.
Application Starts
↓
Redux Provider
↓
Restore Stored Token
↓
Call Protected Profile API
↓
PHP Verifies JWT
↓
Valid?
↙ ↘
Yes No
↓ ↓
User Data Logout
↓
Dispatch login()
↓
Redux Auth State
↓
isAuthenticated = true
↓
App Navigator
↓
Protected Screens
↓
Axios Protected Requests
Logout:
Protected Screen
↓
Remove Token
↓
Dispatch logout()
↓
isAuthenticated = false
↓
Auth Navigator
Redux Toolkit, AsyncStorage, Axios, JWT authentication, and React Navigation can work together to create a complete authentication system for a React Native application.
useSelector reads authentication state.useDispatch sends login and logout actions.Question: Which Redux Toolkit function is commonly used to create an authentication slice?