In this tutorial, you’ll learn how to integrate JWT-based authentication in a React Native app. By the end of today’s lesson, users will be able to log in and access protected routes using a JWT token that is stored securely.
What You Will Learn Today:
- Setting up a login form
- Authenticating a user with JWT and an API
- Storing the JWT securely using AsyncStorage
- Accessing protected routes with the stored JWT
- Logging out and clearing the JWT
Step 1: Setting Up a Login Form
Let’s create a simple login form where users can enter their credentials. We’ll use mock credentials in this example, but you can connect it to an actual API.
- Create a new file called
LoginScreen.js
:
touch LoginScreen.js
- Open
LoginScreen.js
and add the login form:
import React, { useState } from 'react';
import { View, Text, TextInput, Button, Alert } from 'react-native';
import { useDispatch } from 'react-redux';
import { loginUser } from './store/actions/authActions'; // Action we will create next
export default function LoginScreen() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const dispatch = useDispatch();
const handleLogin = () => {
if (!email || !password) {
Alert.alert('Error', 'Please enter both email and password');
return;
}
dispatch(loginUser({ email, password }));
};
return (
<View style={{ flex: 1, justifyContent: 'center', padding: 20 }}>
<Text>Email:</Text>
<TextInput
style={{ borderWidth: 1, marginBottom: 10, padding: 8 }}
onChangeText={setEmail}
value={email}
keyboardType="email-address"
autoCapitalize="none"
/>
<Text>Password:</Text>
<TextInput
style={{ borderWidth: 1, marginBottom: 20, padding: 8 }}
onChangeText={setPassword}
value={password}
secureTextEntry
/>
<Button title="Login" onPress={handleLogin} />
</View>
);
}
Explanation:
- handleLogin: Validates the email and password inputs. If valid, it dispatches the
loginUser
action, which we’ll create in the next step. - TextInput: Captures user inputs for the email and password fields.
Step 2: Creating the Authentication Action
Now, let’s create an action to handle the login process. This action will send the login credentials to the API, receive a JWT token if successful, and store it in Redux.
- Inside the
store/actions
folder, create a new file calledauthActions.js
:
touch store/actions/authActions.js
- Open
authActions.js
and add the login action:
import AsyncStorage from '@react-native-async-storage/async-storage';
export const loginUser = (credentials) => {
return async (dispatch) => {
dispatch({ type: 'LOGIN_REQUEST' });
try {
const response = await fetch('https://yourapi.com/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials),
});
const data = await response.json();
if (data.token) {
await AsyncStorage.setItem('token', data.token); // Store token securely
dispatch({ type: 'LOGIN_SUCCESS', payload: data.token });
} else {
dispatch({ type: 'LOGIN_FAILURE', error: data.message || 'Login failed' });
}
} catch (error) {
dispatch({ type: 'LOGIN_FAILURE', error: error.message });
}
};
};
Explanation:
- loginUser: This function sends the login credentials to the API. If the login is successful, it stores the JWT token in AsyncStorage and dispatches a success action.
- AsyncStorage.setItem(): Stores the token securely on the device so it persists across app sessions.
- LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_FAILURE: These action types handle the various states of the login process (loading, success, and failure).
Step 3: Creating the Authentication Reducer
Now, let’s create a reducer to manage the authentication state. This reducer will store the JWT token in the Redux state upon a successful login.
- Inside the
reducers
folder, create a file calledauthReducer.js
:
touch store/reducers/authReducer.js
- Open
authReducer.js
and define the reducer:
const initialState = {
token: null,
loading: false,
error: null,
};
const authReducer = (state = initialState, action) => {
switch (action.type) {
case 'LOGIN_REQUEST':
return { ...state, loading: true, error: null };
case 'LOGIN_SUCCESS':
return { ...state, token: action.payload, loading: false };
case 'LOGIN_FAILURE':
return { ...state, loading: false, error: action.error };
case 'LOGOUT':
return { ...state, token: null, loading: false, error: null };
default:
return state;
}
};
export default authReducer;
Explanation:
- token: Stores the JWT token upon successful login.
- loading: Tracks the loading state during the login process.
- error: Stores any error message if the login fails.
- LOGOUT: Clears the token from the state when the user logs out.
Combining the Reducer
- Open
reducers/index.js
and add theauthReducer
:
import { combineReducers } from 'redux';
import counterReducer from './counterReducer';
import postReducer from './postReducer';
import authReducer from './authReducer'; // Add the auth reducer
const rootReducer = combineReducers({
counter: counterReducer,
posts: postReducer,
auth: authReducer, // Combine it with other reducers
});
export default rootReducer;
Step 4: Accessing Protected Routes with JWT
Now, let’s create a HomeScreen component that is accessible only when a user is authenticated.
- Create a new file called
HomeScreen.js
:
touch HomeScreen.js
- Open
HomeScreen.js
and create a basic component:
import React from 'react';
import { View, Text, Button } from 'react-native';
import { useDispatch } from 'react-redux';
import AsyncStorage from '@react-native-async-storage/async-storage';
export default function HomeScreen() {
const dispatch = useDispatch();
const handleLogout = async () => {
await AsyncStorage.removeItem('token'); // Remove the token from AsyncStorage
dispatch({ type: 'LOGOUT' }); // Dispatch logout action
};
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Welcome to the protected Home Screen!</Text>
<Button title="Logout" onPress={handleLogout} />
</View>
);
}
Explanation:
- handleLogout: Removes the JWT token from AsyncStorage and dispatches the
LOGOUT
action to clear the token from the Redux store. - This screen can now be accessed only when the token is present in the Redux state.
Step 5: Navigating Based on Authentication State
We’ll now conditionally render either the LoginScreen or the HomeScreen based on whether the user is authenticated (i.e., if the token exists in the Redux state).
- Modify
App.js
as follows:
import React from 'react';
import { Provider, useSelector } from 'react-redux';
import store from './store/store';
import LoginScreen from './LoginScreen';
import HomeScreen from './HomeScreen';
function AppContent() {
const token = useSelector((state) => state.auth.token);
return token ? <HomeScreen /> : <LoginScreen />;
}
export default function App() {
return (
<Provider store={store}>
<AppContent />
</Provider>
);
}
Explanation:
- AppContent: Conditionally renders either the HomeScreen if the token exists or the LoginScreen if it doesn’t.
- This ensures that users are redirected to the login screen if they aren’t authenticated.
Step 6: Testing Authentication
Let’s run the app and test the JWT-based authentication flow.
- Run your app:
npm start
- Enter your login credentials in the LoginScreen. If successful, you should be redirected to the HomeScreen.
- Test the logout button, which should log you out and redirect you back to the LoginScreen.
Step 7: Recap and Summary
In today’s tutorial, you learned how to implement JWT-based authentication in a React Native app. Here’s a quick summary of what you’ve done:
- Created a LoginScreen with a form to capture user credentials.
- Implemented a loginUser action to authenticate the user with an API.
- Stored the JWT token securely using AsyncStorage.
- Created an authReducer to manage authentication state in Redux.
- Built a HomeScreen that is accessible only when the user is authenticated.
- Conditionally rendered the login and home screens based on the authentication state.
With JWT-based authentication, your app can now securely log in users and control access to protected routes.
Next Up: Day 6 – Offline Data Storage with Redux Persist and AsyncStorage
In Day 6, we’ll explore offline data storage in React Native using Redux Persist and AsyncStorage, so users can continue using the app without internet connectivity.
Stay tuned for more advanced features tomorrow!