Hello Devs 👋 ! . We will implement TOTP (Time-based One Time Password) in this post using Spring Security and Angular. We’ll generate QR codes recognizable by Google Authenticator and verify single sign-on with two-factor authentication.
· Prerequisites
· Overview
∘ What is TOTP?
∘ How does TOTP work?
∘ TOTP algorithm
· Let’s get to the code
∘ TOTP Flow
∘ Implementation
· Conclusion
· References
Prerequisites
This is the list of all the prerequisites:
- Spring Boot / WebFlux 3+
- Maven 3.6.3
- Java 21
- MongoDB instance (v6 or later) installed
- Basic knowledge of Angular and Typescript (Angular 18 or later)
- IntelliJ IDEA, Visual Studio Code, or another IDE
Overview
What is TOTP?
Time-based one-time password (TOTP) is a computer algorithm that generates a one-time password (OTP) using the current time as a source of uniqueness. As an extension of the HMAC-based one-time password algorithm (HOTP), it has been adopted as Internet Engineering Task Force (IETF) standard RFC6238. — https://en.wikipedia.org/wiki/Time-based_one-time_password
How does TOTP work?
- Shared Secret: Both the client (e.g., your phone or authentication app) and the server (e.g., the website or service) share a secret key. This key is used to generate the password.
- Time-based Algorithm: The TOTP algorithm uses the current time (usually in 30-second intervals) as a factor in generating the one-time password. It combines the shared secret and the current timestamp to generate a unique password.
- Dynamic Password: The generated password changes every 30 seconds (or another configured time interval). This means that even if someone intercepts the password, it will quickly become useless since it expires so soon.
- Authentication Process: When logging in, the user enters their regular username and password, followed by the TOTP, which is typically displayed on an authenticator app (such as Google Authenticator or Authy). The server checks the TOTP against the one it generates using the shared secret and the current time. If they match, access is granted.
TOTP algorithm
The algorithm (specified by RFC 6238) is an extension of the HOTP
algorithm that uses a time-based moving factor instead of an event counter. It uses a form of symmetric key cryptography: both parties use the same key to generate and validate the token.
Let’s get to the code
TOTP Flow
For this story, we will use the following flow:

Implementation
REST API Setup
We will start by creating a simple Spring Boot project from start.spring.io, with the following dependencies: Spring Reactive Web, Spring Data Reactive MongoDB, Spring Security, Lombok, and Validation.
Then, Add the following dependencies:
<-- library for generating QR codes. -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.5.3</version>
</dependency>
<--JSON Web Token for Java -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>compile</scope>
<version>0.11.5</version>
</dependency>
I used the TOTP Java classes by Taimos GmbH in the project.
Here are the project structure and swagger endpoints.


Angular App
Let’s open cmd and use Angular CLI to create a new Angular Project as the following command:
$ ng new angular-demo-otp
? Which stylesheet format would you like to use? Sass (SCSS) [ https://sass-lang.com/documentation/syntax#scss ]
? Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? no
We also need to generate some Components and Services:
$ ng g s /services/auth
$ ng g s /services/user
$ ng g c /pages/register
$ ng g c /pages/login
$ ng g c /pages/otp
$ ng g c /pages/home
$ ng g c /pages/profile
Define the routes page
import { Routes } from '@angular/router';
import { AuthGuard } from './guards/auth.guard';
export const routes: Routes = [
{ path: '', redirectTo: '/login', pathMatch: 'full' },
{
path: 'login',
loadComponent: () => import('./pages/login/login.component').then(m => m.LoginComponent)
},
{
path: 'register',
loadComponent: () => import('./pages/register/register.component').then(m => m.RegisterComponent)
},
{
path: 'otp',
loadComponent: () => import('./pages/otp/otp.component').then(m => m.OtpComponent),
canActivate: [AuthGuard]
},
{
path: 'home',
loadComponent: () => import('./pages/home/home.component').then(m => m.HomeComponent),
canActivate: [AuthGuard]
},
{
path: 'profile',
loadComponent: () => import('./pages/profile/profile.component').then(m => m.ProfileComponent),
canActivate: [AuthGuard]
}
];

- Register form

@Override
public Mono<UserDTO> createUser(UserRegisterDTO userRegisterDTO) {
return userRepository.findOneByUsernameIgnoreCase(userRegisterDTO.email())
.flatMap(existingUser -> Mono.error(new EmailAlreadyUsedException()))
.cast(User.class)
.switchIfEmpty(Mono.defer(() -> {
String encryptedPassword = passwordEncoder.encode(userRegisterDTO.password());
var newUser = User.builder()
.fullName(userRegisterDTO.fullname())
.username(userRegisterDTO.email())
.password(encryptedPassword)
.roles(Collections.singleton(RoleEnum.USER))
.enabled(true)
.accountNonLocked(false)
.accountNonExpired(false)
.credentialsNonExpired(false)
.build();
return userRepository.save(newUser);
})
)
.map(mapper::toDto);
}
@Injectable({
providedIn: 'root'
})
export class AuthService {
private readonly http = inject(HttpClient);
register(user: UserRegister): Observable<ApiResponse> {
return this.http.post<ApiResponse>(`${environment.apiUrl}/auth/register`, user);
}
}
The user successfully created in the MongoDB database.

- Login
On first login, the 2-factor option is not enabled. The user must enable it in profile settings.

- Profile settings
When enabling the two-factor authentication option, the backend generates a QR code with the secret key, which is returned to the front end in base64.

Users must install the Google Authenticator app (Available on the App Store and Play Marke) to scan and retrieve the verification code. After scanning this QR code you should see a new entry in the Google Authenticator entry list.
Now, for the next login, the user must verify the code from the Authenticator app. If the user disables 2FA, the secret key will also be deleted from the database

- Two-factor authentication

The complete source code is available on GitHub.
Conclusion
Well done !!. In this post, we implemented the TOTP-based Two-Factor Authentication using Spring Reactive and Angular.
Support me through GitHub Sponsors.
Thank you for reading!! See you in the next post.
References
- https://datatracker.ietf.org/doc/html/rfc6238
- https://github.com/taimos/totp
- https://authenticator.cc/
👉 Link to Medium blog
