Authentication
Now that your React Native app is up and running and can communicate with your Node.js backend, it's time to add a layer of security—authentication. Imagine your app is a cozy little café; you wouldn't want just anyone walking in without a valid reason. Authentication ensures that only the right people have access to your app's features and data.
In this chapter, we’ll dive into user authentication by implementing a simple login and registration system. We’ll use JSON Web Tokens (JWT) for secure communication between the client and server. Don't worry if that sounds complicated; we'll break it down step by step.
By the end of this chapter, your app will allow users to register, log in, and access protected routes. Let’s get started!
Step 1: Setting Up User Registration and Login Routes in Node.js
First, we need to set up the backend to handle user registration and login. This involves creating new routes in your Node.js application that will accept user credentials, verify them, and respond accordingly.
Installing Required Packages
Before we dive into coding, let’s install a couple of packages. Open your terminal and run the following command:
npm install bcryptjs jsonwebtoken
- bcryptjs: This package will help us hash user passwords before storing them, making it much harder for anyone to steal passwords.
- jsonwebtoken: This will allow us to create and verify JWTs for our authentication system.
Creating Registration and Login Routes
Open your server.js
(or whichever file you’re using to set up your server) and add the following code to create the routes:
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const app = express();
const PORT = 3000;
const SECRET_KEY = 'your-secret-key'; // Change this to something more secure
app.use(express.json());
let users = []; // Simulated database for users
// Registration Route
app.post('/register', async (req, res) => {
const { name, email, password } = req.body;
// Check if user already exists
const existingUser = users.find(user => user.email === email);
if (existingUser) {
return res.status(400).json({ message: 'User already exists' });
}
// Hash password
const hashedPassword = await bcrypt.hash(password, 10);
// Create new user and add to database
const newUser = { name, email, password: hashedPassword };
users.push(newUser);
res.status(201).json({ message: 'User registered successfully' });
});
// Login Route
app.post('/login', async (req, res) => {
const { email, password } = req.body;
// Find user by email
const user = users.find(user => user.email === email);
if (!user) {
return res.status(400).json({ message: 'Invalid email or password' });
}
// Verify password
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
return res.status(400).json({ message: 'Invalid email or password' });
}
// Generate JWT token
const token = jwt.sign({ email: user.email }, SECRET_KEY, { expiresIn: '1h' });
res.status(200).json({ token });
});
// Start the server
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});
Explanation:
-
User Registration:
- The
/register
route accepts a POST request with user details. - It checks if the user already exists by looking for their email.
- If not, it hashes the password and stores the new user in our simulated database.
- The
-
User Login:
- The
/login
route checks if the user exists and verifies the password using bcrypt. - If the password is correct, it generates a JWT token using the user's email and sends it back to the client.
- The
Step 2: Adding Authentication to Your React Native App
Now that the backend is set up, let’s modify your React Native app to allow users to register and log in.
Updating Your App to Include Registration and Login Forms
Open your App.js
file and update it to include registration and login forms. Here’s the updated code:
import React, { useState } from 'react';
import { StyleSheet, Text, View, TextInput, Button, FlatList, Alert } from 'react-native';
import axios from 'axios';
export default function App() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [token, setToken] = useState('');
const registerUser = () => {
axios.post('http://192.168.x.x:3000/register', { name, email, password })
.then(response => {
Alert.alert('Success', response.data.message);
setName('');
setEmail('');
setPassword('');
})
.catch(error => {
Alert.alert('Error', error.response.data.message);
});
};
const loginUser = () => {
axios.post('http://192.168.x.x:3000/login', { email, password })
.then(response => {
setToken(response.data.token);
setIsLoggedIn(true);
Alert.alert('Success', 'Logged in successfully');
})
.catch(error => {
Alert.alert('Error', error.response.data.message);
});
};
return (
<View style={styles.container}>
<Text style={styles.title}>User Authentication</Text>
{isLoggedIn ? (
<View>
<Text style={styles.welcome}>Welcome!</Text>
<Text>Your token: {token}</Text>
</View>
) : (
<View>
<TextInput
style={styles.input}
placeholder="Enter name"
value={name}
onChangeText={setName}
/>
<TextInput
style={styles.input}
placeholder="Enter email"
value={email}
onChangeText={setEmail}
/>
<TextInput
style={styles.input}
placeholder="Enter password"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
<Button title="Register" onPress={registerUser} />
<Button title="Login" onPress={loginUser} />
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingTop: 50,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
},
input: {
width: '80%',
height: 40,
borderColor: 'gray',
borderWidth: 1,
marginBottom: 10,
paddingHorizontal: 10,
},
welcome: {
fontSize: 20,
fontWeight: 'bold',
},
});
Breakdown of Changes:
-
State Variables:
- Added state variables for
name
,password
,isLoggedIn
, andtoken
.
- Added state variables for
-
Registration and Login Functions:
registerUser
function sends user data to the backend for registration.loginUser
function sends email and password to log in and retrieves the JWT token.
-
Conditional Rendering:
- The UI switches between registration/login forms and a welcome message based on the
isLoggedIn
state.
- The UI switches between registration/login forms and a welcome message based on the
Step 3: Storing the Token Securely
For security, it’s important to store the JWT token securely on the device. We’ll use the AsyncStorage
library for this purpose. Install it by running:
npm install @react-native-async-storage/async-storage
Next, import AsyncStorage
in your App.js
:
import AsyncStorage from '@react-native-async-storage/async-storage';
Now, update the loginUser
function to save the token:
const loginUser = () => {
axios.post('http://192.168.x.x:3000/login', { email, password })
.then(response => {
const userToken = response.data.token;
setToken(userToken);
setIsLoggedIn(true);
AsyncStorage.setItem('userToken', userToken); // Store token securely
Alert.alert('Success', 'Logged in successfully');
})
.catch(error => {
Alert.alert('Error', error.response.data.message);
});
};
To retrieve the token when the app starts, you can add the following code inside a useEffect
hook:
import { useEffect } from 'react';
useEffect(() => {
const getToken = async () => {
const storedToken = await AsyncStorage.getItem('userToken');
if (storedToken) {
setToken(storedToken);
setIsLoggedIn(true);
}
};
getToken();
}, []);
This code checks for a stored token when the app loads and sets the user to logged in if a valid token exists.
Step 4: Protecting Routes in Node.js
Now that your React Native app can authenticate users, let’s protect your Node.js routes. We want to ensure that certain routes are only accessible if the user is logged in. We’ll create a middleware function to do this.
Add the following middleware function to your server.js
file:
const authenticateToken = (req, res, next) => {
const token = req.headers['authorization']?.split(' ')[1];
if (!token) return res.sendStatus(401); // Unauthorized
jwt.verify(token, SECRET_KEY, (err, user) => {
if (err) return res.sendStatus(403); // Forbidden
req.user = user;
next();
});
};
// Example protected route
app.get('/protected', authenticateToken, (req, res) => {
res.json({ message: 'This is protected data!', user: req.user });
});
Explanation of the Middleware:
authenticateToken
: This function checks for a token in the request headers. If there’s no token, it sends a 401 status.- If a token is present, it verifies it. If valid, it allows access to the next route; if not, it sends a 403 status.
You can test the protected route by sending a GET request to /protected
with the token in the authorization header.
Step 5: Enhancing User Experience with Logout
Finally, let’s add a logout feature to your app. This is a good way to let users end their session.
Add a logoutUser
function in your App.js
:
const logoutUser = async () => {
await AsyncStorage.removeItem('userToken'); // Remove token
setIsLoggedIn(false); // Set logged in state to false
setToken(''); // Clear token
Alert.alert('Success', 'Logged out successfully');
};
You can add a button to call this function when users want to log out:
<Button title="Logout" onPress={logoutUser} />
Now, when the user logs out, the app clears the token and sets the user state back to logged out.
Step 6: Testing Your Authentication System
Now that everything is set up, it’s time to test your authentication system!
- Start your Node.js server by running
node server.js
. - Open your React Native app.
- Try registering a new user and then logging in with that user.
- After logging in, test accessing protected routes using tools like Postman or directly from your app.
- Don’t forget to test logging out and ensuring everything works as expected.
Step 7: Wrapping Up
Congratulations! You’ve now added user authentication to your React Native app with Node.js. This chapter laid the groundwork for building secure applications that handle user data responsibly.
In the next chapter, we’ll dive even deeper and explore how to integrate a database to store user information persistently. This way, your user data won’t disappear when the server restarts.
🚀