Prerequisites
Before we start, ensure you have the following:
- Java Development Kit (JDK) installed
- Apache Maven installed
- Node.js and npm installed
- An IDE (such as IntelliJ IDEA, Eclipse, or VS Code) installed
Step 1: Setting Up the Spring Boot Project
1.1 Create a Spring Boot Project
-
Open Spring Initializr:
- Go to Spring Initializr in your web browser.
-
Configure Project Metadata:
- Project: Maven Project
- Language: Java
- Spring Boot: Select the latest version of Spring Boot
- Group: com.example
- Artifact: spring-boot-react-auth
- Name: spring-boot-react-auth
- Description: Full Stack Application with Spring Boot and React for User Registration and Login
- Package Name: com.example.springbootreactauth
- Packaging: Jar
- Java Version: 17 (or your preferred version)
- Click
Next
.
-
Select Dependencies:
- On the
Dependencies
screen, select the dependencies you need. For user authentication, you can start with:- Spring Web
- Spring Security
- Spring Data JPA
- H2 Database
- Spring Boot DevTools
- Click
Next
.
- On the
-
Generate the Project:
- Click
Generate
to download the project zip file. - Extract the zip file to your desired location.
- Click
-
Open the Project in Your IDE:
- Open your IDE and import the project as a Maven project.
1.2 Project Structure
After importing the project, you will see the following structure in your IDE:
spring-boot-react-auth
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── springbootreactauth
│ │ │ ├── SpringBootReactAuthApplication.java
│ │ │ ├── config
│ │ │ ├── controller
│ │ │ ├── model
│ │ │ ├── repository
│ │ │ └── service
│ ├── main
│ │ └── resources
│ │ ├── application.properties
│ └── test
│ └── java
│ └── com
│ └── example
│ └── springbootreactauth
│ └── SpringBootReactAuthApplicationTests.java
└── pom.xml
Step 2: Creating the Backend
2.1 Configure H2 Database
Open the application.properties
file located in the src/main/resources
directory and add the following configuration:
# H2 Database configuration
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
# JPA settings
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
2.2 Create the User
Entity
In the model
package, create a new Java class named User
:
package com.example.springbootreactauth.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private String email;
private String password;
private String role;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
}
2.3 Create the UserRepository
Interface
In the repository
package, create a new Java interface named UserRepository
:
package com.example.springbootreactauth.repository;
import com.example.springbootreactauth.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(String email);
}
2.4 Create the UserService
Interface
In the service
package, create a new Java interface named UserService
:
package com.example.springbootreactauth.service;
import com.example.springbootreactauth.model.User;
public interface UserService {
User findByEmail(String email);
User saveUser(User user);
}
2.5 Implement the UserService
Interface
In the service
package, create a new Java class named UserServiceImpl
:
package com.example.springbootreactauth.service;
import com.example.springbootreactauth.model.User;
import com.example.springbootreactauth.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Autowired
public UserServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
@Override
public User findByEmail(String email) {
return userRepository.findByEmail(email);
}
@Override
public User saveUser(User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
return userRepository.save(user);
}
}
2.6 Configure Spring Security
Create a new Java class named SecurityConfig
in the config
package:
package com.example.springbootreactauth.config;
import com.example.springbootreactauth.model.User;
import com.example.springbootreactauth.service.UserServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final UserServiceImpl userService;
public SecurityConfig(UserServiceImpl userService) {
this.userService = userService;
}
@Bean
public UserDetailsService userDetailsService() {
return email -> {
User user = userService.findByEmail(email);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return org.springframework.security.core.userdetails.User
.withUsername(user.getEmail())
.password(user.getPassword())
.roles(user.getRole())
.build();
};
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(authorizeRequests ->
authorizeRequests.requestMatchers("/api/register", "/api/login").permitAll()
.anyRequest().authenticated()
)
.httpBasic();
return http.build();
}
}
2.7 Create the UserController
Class
In the controller
package, create a new Java class named UserController
:
package com.example.springbootreactauth.controller;
import com.example.springbootreactauth.model.User;
import com.example.springbootreactauth.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class UserController {
private final UserService userService;
private final PasswordEncoder passwordEncoder;
@Autowired
public UserController(UserService userService, PasswordEncoder passwordEncoder) {
this.userService = userService;
this.passwordEncoder = passwordEncoder;
}
@PostMapping("/register")
public String register(@RequestBody User user) {
User existingUser = userService.findByEmail(user.getEmail());
if (existingUser != null) {
return "Email already exists";
}
userService.saveUser(user);
return "User registered successfully";
}
@PostMapping("/login")
public String login(@RequestBody User user) {
User existingUser = userService.findByEmail(user.getEmail());
if (existingUser != null && passwordEncoder.matches(user.getPassword(), existingUser.getPassword())) {
return "Login successful";
} else {
return "Invalid credentials";
}
}
}
Step 3: Creating the Frontend with React
3.1 Set Up React Project
-
Open a terminal and navigate to your workspace directory.
-
Create a new React project using Create React App:
npx create-react-app react-frontend
-
Navigate to the project directory:
cd react-frontend
3.2 Install Axios and React Router DOM
Install Axios to make HTTP requests and React Router DOM for routing:
npm install axios react-router-dom@6
3.3 Install Bootstrap
Install Bootstrap for styling:
npm install bootstrap
3.4 Create Components
Create the necessary components for the user registration and login functionalities.
3.4.1 Create AuthService.js
Create a new file AuthService.js
in the src
directory to handle API requests:
import axios from 'axios';
const API_BASE_URL = "http://localhost:8080/api";
class AuthService {
register(user) {
return axios.post(`${API_BASE_URL}/register`, user);
}
login(credentials) {
return axios.post(`${API_BASE_URL}/login`, credentials);
}
}
export default new AuthService();
3.4.2 Create RegisterComponent.js
Create a new file RegisterComponent.js
in the src/components
directory:
import React, { useState } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import AuthService from '../AuthService';
import 'bootstrap/dist/css/bootstrap.min.css';
const RegisterComponent = () => {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [message, setMessage] = useState('');
const navigate = useNavigate();
const handleRegister = async (e) => {
e.preventDefault();
try {
const response = await AuthService.register({ firstName, lastName, email, password });
setMessage(response.data);
if (response.data === 'User registered successfully') {
navigate('/login');
}
} catch (error) {
setMessage('Registration failed');
}
};
return (
<div className="container mt-5">
<div className="row justify-content-center">
<div className="col-md-6">
<div className="card">
<div className="card-header">Registration</div>
<div className="card-body">
{message && <div className="alert alert-info">{message}</div>}
<form onSubmit={handleRegister}>
<div className="form-group">
<label>First Name</label>
<input
type="text"
className="form-control"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
</div>
<div className="form-group">
<label>Last Name</label>
<input
type="text"
className="form-control"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
</div>
<div className="form-group">
<label>Email</label>
<input
type="email"
className="form-control"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className="form-group">
<label>Password</label>
<input
type="password"
className="form-control"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<button type="submit" className="btn btn-primary">Register</button>
</form>
<div className="mt-3">
<span>Already registered? <Link to="/login">Login here</Link></span>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default RegisterComponent;
3.4.3 Create LoginComponent.js
Create a new file LoginComponent.js
in the src/components
directory:
import React, { useState } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import AuthService from '../AuthService';
import 'bootstrap/dist/css/bootstrap.min.css';
const LoginComponent = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [message, setMessage] = useState('');
const navigate = useNavigate();
const handleLogin = async (e) => {
e.preventDefault();
try {
const response = await AuthService.login({ email, password });
if (response.data === 'Login successful') {
navigate('/dashboard');
} else {
setMessage('Invalid credentials');
}
} catch (error) {
setMessage('Invalid credentials');
}
};
return (
<div className="container mt-5">
<div className="row justify-content-center">
<div className="col-md-6">
<div className="card">
<div className="card-header">Login Form</div>
<div className="card-body">
{message && <div className="alert alert-danger">{message}</div>}
<form onSubmit={handleLogin}>
<div className="form-group">
<label>Email</label>
<input
type="email"
className="form-control"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className="form-group">
<label>Password</label>
<input
type="password"
className="form-control"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<button type="submit" className="btn btn-primary">Login</button>
</form>
<div className="mt-3">
<span>Not registered? <Link to="/register">Register here</Link></span>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default LoginComponent;
3.4.4 Create DashboardComponent.js
Create a new file DashboardComponent.js
in the src/components
directory:
import React from 'react';
const DashboardComponent = () => {
return (
<div className="container mt-5">
<h2>Dashboard</h2>
<p>Welcome to the dashboard!</p>
</div>
);
};
export default DashboardComponent;
3.4.5 Create App.js
Modify the App.js
file to set up routing for the application:
import React from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import RegisterComponent from './components/RegisterComponent';
import LoginComponent from './components/LoginComponent';
import DashboardComponent from './components/DashboardComponent';
const App = () => {
return (
<Router>
<div className="container">
<Routes>
<Route path="/" element={<LoginComponent />} />
<Route path="/register" element={<RegisterComponent />} />
<Route path="/login" element={<LoginComponent />} />
<Route path="/dashboard" element={<DashboardComponent />} />
</Routes>
</div>
</Router>
);
};
export default App;
3.4.6 Update index.js
Ensure the index.js
file is set up correctly:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import 'bootstrap/dist/css/bootstrap.min.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Step 4: Running the Application
4.1 Run the Spring Boot Application
- Open the
SpringBootReactAuthApplication
class in thesrc/main/java/com/example/springbootreactauth
directory. - Click the green
Run
button in your IDE or use the terminal to run the
application:
./mvnw spring-boot:run
4.2 Run the React Application
-
Open a terminal and navigate to the
react-frontend
directory. -
Start the React application:
npm start
-
Open your web browser and navigate to
http://localhost:3000
.
You can now use the registration and login functionalities provided by the React frontend and Spring Boot backend.
Conclusion
In this tutorial, we created a full-stack application using Spring Boot for the backend and React (with functional components and hooks) for the frontend. We implemented user registration and login functionalities using Spring Security 6+ and created a simple registration and login page with React. This setup provides a solid foundation for developing more complex full-stack applications with user authentication.
Comments
Post a Comment
Leave Comment