Prerequisites
Before we start, ensure you have the following:
- Java Development Kit (JDK) installed
- Apache Maven installed
- Node.js and npm installed
- Angular CLI installed (
npm install -g @angular/cli
) - An IDE (such as IntelliJ IDEA, Eclipse, or VS Code) installed
Step 1: Setting Up the Spring Boot Backend
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 3.3
- Group: com.example
- Artifact: auth-service
- Name: auth-service
- Description: Authentication Service
- Package Name: com.example.authservice
- Packaging: Jar
- Java Version: 17 (or your preferred version)
- Click
Next
.
-
Select Dependencies:
- On the
Dependencies
screen, select:- 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 Update application.properties
Open the application.properties
file located in the src/main/resources
directory and add the following configuration:
server.port=8080
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
spring.jpa.hibernate.ddl-auto=update
Explanation:
- Configures the server port to 8080.
- Sets up an in-memory H2 database.
- Enables the H2 console for database inspection.
- Automatically updates the database schema based on JPA entities.
1.3 Create User Entity
Create a User
entity class in the com.example.authservice.model
package:
package com.example.authservice.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 username;
private String password;
private String email;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
Explanation:
- A simple
User
class withid
,username
,password
, andemail
fields, along with their getters and setters.
1.4 Create User Repository
Create a UserRepository
interface in the com.example.authservice.repository
package:
package com.example.authservice.repository;
import com.example.authservice.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
Explanation:
@Repository
: Marks the interface as a Spring Data repository.- Extends
JpaRepository<User, Long>
: Provides CRUD operations for theUser
entity. findByUsername(String username)
: Custom query method to find a user by username.
1.5 Create User Service
Create a UserService
class in the com.example.authservice.service
package that implements UserDetailsService
:
package com.example.authservice.service;
import com.example.authservice.model.User;
import com.example.authservice.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
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.stereotype.Service;
import java.util.Optional;
@Service
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
this.passwordEncoder = new BCryptPasswordEncoder();
}
public User saveUser(User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
return userRepository.save(user);
}
public Optional<User> findByUsername(String username) {
return Optional.ofNullable(userRepository.findByUsername(username));
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword())
.authorities("USER")
.build();
}
}
Explanation:
@Service
: Marks the class as a service component.- Implements
UserDetailsService
: Required by Spring Security to load user-specific data. loadUserByUsername(String username)
: Loads user details for authentication.saveUser(User user)
: Saves a new user with an encoded password.findByUsername(String username)
: Finds a user by username.
1.6 Create Security Configuration
Create a SecurityConfig
class in the com.example.authservice.config
package:
package com.example.authservice.config;
import com.example.authservice.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
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.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
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 UserService userService;
@Autowired
public SecurityConfig(UserService userService) {
this.userService = userService;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
return http.build();
}
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class).build();
}
}
Explanation:
@Configuration
: Marks the class as a configuration component.@EnableWebSecurity
: Enables Spring Security.securityFilterChain(HttpSecurity http)
: Configures HTTP security, disabling CSRF and permitting access to/auth/**
endpoints.configure(AuthenticationManagerBuilder auth)
: Configures the authentication manager with user details service and password encoder.passwordEncoder()
: Configures the password encoder.authenticationManager(HttpSecurity http)
: Provides the authentication manager.
1.7 Create Authentication Controller
Create an AuthController
class in the com.example.authservice.controller
package:
package com.example.authservice.controller;
import com.example.authservice.model.User;
import com.example.authservice.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/auth")
@CrossOrigin(origins = "http
://localhost:4200")
public class AuthController {
private final AuthenticationManager authenticationManager;
private final UserService userService;
@Autowired
public AuthController(AuthenticationManager authenticationManager, UserService userService) {
this.authenticationManager = authenticationManager;
this.userService = userService;
}
@PostMapping("/register")
public ResponseEntity<?> registerUser(@RequestBody User user) {
if (userService.findByUsername(user.getUsername()).isPresent()) {
return ResponseEntity.badRequest().body("Username is already taken");
}
userService.saveUser(user);
return ResponseEntity.ok("User registered successfully");
}
@PostMapping("/login")
public ResponseEntity<?> loginUser(@RequestBody User user) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return ResponseEntity.ok(userDetails.getUsername());
} catch (AuthenticationException e) {
return ResponseEntity.status(401).body("Invalid username or password");
}
}
@PostMapping("/logout")
public ResponseEntity<?> logoutUser() {
SecurityContextHolder.clearContext();
return ResponseEntity.ok("User logged out successfully");
}
}
Explanation:
@RestController
: Marks the class as a REST controller.@RequestMapping("/auth")
: Maps requests to/auth
URL.@CrossOrigin(origins = "http://localhost:4200")
: Enables CORS for requests from the Angular frontend running onlocalhost:4200
.registerUser(@RequestBody User user)
: Handles user registration.loginUser(@RequestBody User user)
: Handles user login.logoutUser()
: Handles user logout.
1.8 Run the Spring Boot Application
Run the application by executing the AuthServiceApplication
class. The backend should be up and running on http://localhost:8080
.
Step 2: Setting Up the Angular Frontend
2.1 Create an Angular Project
- Open a terminal and run the following command to create a new Angular project:
ng new auth-client
- Navigate to the project directory:
cd auth-client
2.2 Install Dependencies
Install Bootstrap for styling:
npm install bootstrap
Add Bootstrap to angular.json
:
"styles": [
"src/styles.css",
"node_modules/bootstrap/dist/css/bootstrap.min.css"
],
2.3 Create Authentication Service
Generate the AuthService
:
ng generate service services/auth
Edit auth.service.ts
:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private baseUrl = 'http://localhost:8080/auth';
constructor(private http: HttpClient) { }
register(user: any): Observable<any> {
return this.http.post(`${this.baseUrl}/register`, user);
}
login(credentials: any): Observable<any> {
return this.http.post(`${this.baseUrl}/login`, credentials, { responseType: 'text' });
}
logout(): Observable<any> {
return this.http.post(`${this.baseUrl}/logout`, {});
}
}
Explanation:
@Injectable({ providedIn: 'root' })
: Marks the service as injectable and available throughout the app.HttpClient
: Service for making HTTP requests.register(user: any)
: Sends a POST request to register a new user.login(credentials: any)
: Sends a POST request to log in a user.logout()
: Sends a POST request to log out a user.
2.4 Create Components
Generate the components for registration, login, and home:
ng generate component components/register
ng generate component components/login
ng generate component components/home
Edit register.component.ts
:
import { Component } from '@angular/core';
import { AuthService } from '../../services/auth.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css']
})
export class RegisterComponent {
user = { username: '', password: '', email: '' };
message = '';
constructor(private authService: AuthService, private router: Router) { }
register() {
this.authService.register(this.user).subscribe(() => {
this.message = 'Registration successful';
this.router.navigate(['/login']);
}, error => {
console.error('Registration error: ', error);
this.message = 'Registration failed';
});
}
}
Edit register.component.html
:
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">Register</div>
<div class="card-body">
<form (ngSubmit)="register()">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" class="form-control" [(ngModel)]="user.username" name="username" required>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" class="form-control" [(ngModel)]="user.email" name="email" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" class="form-control" [(ngModel)]="user.password" name="password" required>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
<div class="mt-3" *ngIf="message">{{ message }}</div>
</div>
</div>
</div>
</div>
</div>
Edit login.component.ts
:
import { Component } from '@angular/core';
import { AuthService } from '../../services/auth.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent {
credentials = { username: '', password: '' };
message = '';
constructor(private authService: AuthService, private router: Router) { }
login() {
this.authService.login(this.credentials).subscribe(response => {
localStorage.setItem('username', response);
this.router.navigate(['/home']);
}, error => {
console.error('Login error: ', error);
this.message = 'Invalid username or password';
});
}
}
Edit login.component.html
:
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">Login</div>
<div class="card-body">
<form (ngSubmit)="login()">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" class="form-control" [(ngModel)]="credentials.username" name="username" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" class="form-control" [(ngModel)]="credentials.password" name="password" required>
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
<div class="mt-3" *ngIf="message">{{ message }}</div>
</div>
</div>
</div>
</div>
</div>
Edit home.component.ts
:
import { Component } from '@angular/core';
import { AuthService } from '../../services/auth.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent {
constructor(private authService: AuthService, private router: Router) { }
logout() {
this.authService.logout().subscribe(() => {
localStorage.removeItem('username');
this.router.navigate(['/login']);
}, error => {
console.error('Logout error: ', error);
});
}
}
Edit home.component.html
:
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">Home</div>
<div class="card-body">
<p>Welcome, {{ localStorage.getItem('username') }}</p>
<button (click)="logout()" class="btn btn-primary">Logout</button>
</div>
</div>
</div>
</div>
</div>
2.5 Create HttpInterceptor
Generate the HttpInterceptor:
ng generate interceptor interceptors/auth
Edit auth.interceptor.ts
:
import {
Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const username = localStorage.getItem('username');
if (username) {
const cloned = req.clone({
headers: req.headers.set('Authorization', `Basic ${btoa(username + ':' + localStorage.getItem('password'))}`)
});
return next.handle(cloned);
}
return next.handle(req);
}
}
Explanation:
HttpInterceptor
: Intercepts HTTP requests to add the Authorization header.
2.6 Update Angular Routing
Edit app-routing.module.ts
:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { RegisterComponent } from './components/register/register.component';
import { LoginComponent } from './components/login/login.component';
import { HomeComponent } from './components/home/home.component';
const routes: Routes = [
{ path: 'register', component: RegisterComponent },
{ path: 'login', component: LoginComponent },
{ path: 'home', component: HomeComponent },
{ path: '', redirectTo: '/login', pathMatch: 'full' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Explanation:
- Defines routes for the registration, login, and home components.
- Redirects the root path to
/login
.
2.7 Update Angular App Module
Edit app.module.ts
:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { RegisterComponent } from './components/register/register.component';
import { LoginComponent } from './components/login/login.component';
import { HomeComponent } from './components/home/home.component';
import { AuthInterceptor } from './interceptors/auth.interceptor';
@NgModule({
declarations: [
AppComponent,
RegisterComponent,
LoginComponent,
HomeComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
HttpClientModule
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
],
bootstrap: [AppComponent]
})
export class AppModule { }
Explanation:
- Imports necessary modules for the Angular app.
- Declares the components used in the app.
- Sets up the app's root module.
- Configures the HTTP interceptor.
2.8 Run the Angular Application
Open a terminal in the Angular project directory and run the application:
ng serve
Visit http://localhost:4200
in your web browser to see the application.
Conclusion
In this tutorial, we created a login authentication system using Spring Boot 3.3 for the backend and Angular 18 for the frontend. We implemented login, registration, and logout functionality and used Angular HttpInterceptor to manage HTTP requests. By following this structure, you can extend and customize the application as needed.
Comments
Post a Comment
Leave Comment