Generating and Consuming REST APIs with Spring Boot 2, Angular 7 and Swagger 2

Generating and Consuming REST APIs with Spring Boot 2, Angular 7 and Swagger 2

<strong>Originally published by </strong><a href="https://medium.com/@swathisprasad" target="_blank">Swathi Prasad</a><strong> </strong><em>at&nbsp;</em><a href="https://medium.com/@swathisprasad/generating-and-consuming-rest-apis-with-spring-boot-2-angular-7-and-swagger-2-ee4abc3c6459" target="_blank"><em>Medium&nbsp;</em></a>

Originally published by Swathi Prasad at Medium 

Imagine you want to integrate backend APIs or third party APIs with minimal effort, how do you pull it off?

Integrating Backend APIs manually can be time consuming and error-prone. Hence, Swagger comes into picture. Swagger is the most popular framework to generate and consume OpenAPI specification. The tool greatly relieves the burden of documenting and interacting with APIs.

In this article, we are going to generate API documentation from Spring Boot REST API and generate Angular API client from the documentation using Swagger.

Tools and Technologies Used

  • Spring Boot — 2.1.4.RELEASE
  • JDK — OpenJDK 12
  • Spring Framework — 5.1.6 RELEASE
  • JPA
  • Hibernate
  • H2 in-memory database
  • Swagger — 2
  • springfox-swagger2–2.9.4
  • springfox-swagger-ui — 2.9.4
  • Swagger Codegen CLI — 2.4.4
  • IDE — IntelliJ and Visual Studio Code
  • Angular CLI — 7.2.0
  • Node — 8.11.3
  • NPM — 6.3.0

Generating Swagger API Documentation

Create a sample Spring Boot application. Here is my sample project structure. I have created the project manually, but you could also create using Spring Intializer.


I will go over each package in a bit.

Spring Boot Project Structure

There are several implementations of Swagger 2 which adheres to Open API specification. Springfox is one of those implementations. Currently, Springfox supports only Swagger 1.2 and Swagger 2.0. Add Springfox dependencies as follows:

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

Here is the complete Maven POM.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
&lt;groupId&gt;com.swathisprasad&lt;/groupId&gt;
&lt;artifactId&gt;springboot-swagger&lt;/artifactId&gt;
&lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;

&lt;parent&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
    &lt;version&gt;2.1.4.RELEASE&lt;/version&gt;
    &lt;relativePath /&gt;
&lt;/parent&gt;

&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
        &lt;exclusions&gt;
            &lt;exclusion&gt;
                &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
                &lt;artifactId&gt;spring-boot-starter-tomcat&lt;/artifactId&gt;
            &lt;/exclusion&gt;
        &lt;/exclusions&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-undertow&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-actuator&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.springfox&lt;/groupId&gt;
        &lt;artifactId&gt;springfox-swagger2&lt;/artifactId&gt;
        &lt;version&gt;2.9.2&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.springfox&lt;/groupId&gt;
        &lt;artifactId&gt;springfox-swagger-ui&lt;/artifactId&gt;
        &lt;version&gt;2.9.2&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;com.h2database&lt;/groupId&gt;
        &lt;artifactId&gt;h2&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;

&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;

</project>

Let us first enable Swagger support in the application. We enable using 

@EnableSwagger2 annotation. Notice that we are specifying base package “com.swathisprasad.springboot”. This is to generate API documentation within our custom package.

If we do not provide base package, Swagger would generate documentation even for Spring Boot actuator endpoints which may not be of our interest.

package com.swathisprasad.springboot.swagger;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

@Bean
public Docket api() {

    return new Docket(DocumentationType.SWAGGER_2).select()
            .apis(RequestHandlerSelectors
                    .basePackage("com.swathisprasad.springboot"))
            .paths(PathSelectors.any())
            .build().apiInfo(apiEndPointsInfo());
}

private ApiInfo apiEndPointsInfo() {

    return new ApiInfoBuilder().title("Spring Boot REST API")
            .description("Language Management REST API")
            .contact(new Contact("Swathi Prasad", "www.techshard.com", "[email protected]"))
            .license("Apache 2.0")
            .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
            .version("1.0-SNAPSHOT")
            .build();
}

}

Before we create an API endpoint, let’s create a JPA entity Language and a corresponding JPA repository ILanguageRepository. I have also created a DataInitializer class to create mock data at server startup.

package com.swathisprasad.springboot.dao.entity;

import javax.persistence.*;

@Entity
public class Language {

@Id
@Column(name = "ID", nullable = false)
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;

private String name;

public Language() {
}

public String getName() {
    return name;
}

public void setName(final String name) {
    this.name = name;
}

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    result = prime * result + (int) (id ^ (id &gt;&gt;&gt; 32));
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (!(obj instanceof Language))
        return false;
    final Language other = (Language) obj;
    if (name == null) {
        if (other.name != null)
            return false;
    } else if (!name.equals(other.name))
        return false;
    return true;
}

}
package com.swathisprasad.springboot.dao.repository;

import com.swathisprasad.springboot.dao.entity.Language;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ILanguageRepository extends JpaRepository<Language, Long> {
}
package com.swathisprasad.springboot;

import com.swathisprasad.springboot.dao.entity.Language;
import com.swathisprasad.springboot.dao.repository.ILanguageRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class DataInitializer {

private final Logger logger = LoggerFactory.getLogger(DataInitializer.class);

@Autowired
ILanguageRepository languageRepository;

public void initData() {

    try {
        Language language = new Language();
        language.setName("Java");
        languageRepository.save(language);

        language = new Language();
        language.setName("JavaScript");
        languageRepository.save(language);

        language = new Language();
        language.setName("C++");
        languageRepository.save(language);

        language = new Language();
        language.setName("Groovy");
        languageRepository.save(language);

        language = new Language();
        language.setName("Python");
        languageRepository.save(language);

        language = new Language();
        language.setName("Swift");
        languageRepository.save(language);


    } catch (final Exception ex) {
        logger.error("Exception while inserting mock data {}", ex);
    }

}

}

Create a REST controller which contains an API endpoint as below. We can customize API documentation using @API, @APIOperation and @APIResponse annotations provided by Springfox as shown below.

package com.swathisprasad.springboot.controller;

import com.swathisprasad.springboot.dao.entity.Language;
import com.swathisprasad.springboot.dao.repository.ILanguageRepository;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@Api(value="Language API", description="Operations pertaining to Language")
@RequestMapping("/api")
public class LanguageController {

@Autowired
ILanguageRepository languageRepository;

@ApiOperation(value = "View a list of available languages", response = Iterable.class)
@ApiResponses(value = {
        @ApiResponse(code = 200, message = "Successfully retrieved list"),
        @ApiResponse(code = 401, message = "You are not authorized to view the resource"),
        @ApiResponse(code = 403, message = "Accessing the resource you were trying to reach is forbidden"),
        @ApiResponse(code = 404, message = "The resource you were trying to reach is not found")
}
)
@GetMapping(value = "/languages", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity&lt;List&lt;Language&gt;&gt; getLanguages() {
    final List&lt;Language&gt; languages = languageRepository.findAll();
    return new ResponseEntity&lt;&gt;(languages, HttpStatus.OK);
}

}

Now, let us run Application class as Java application. Note that we will see below warning when Undertow server is started. Undertow complains about XNIO Illegal reflective access since Java 9+ and the issue has been reported here.

WARNING: An illegal reflective access operation has occurred

Once the server has started successfully, open http://localhost:8080/v2/api-docs in the browser. We will see Swagger documentation and save it as JSON on your file system.

This documentation adheres to OpenAPI 2.0 specification.

The Swagger UI can be viewed at http://localhost:8080/swagger-ui.html .

NOTE: If you wish to generate OpenAPI 3.0 spec, the above generated spec can also be imported and converted into OpenAPI 3.0 spec at SwaggerHub.

Generating Angular API Clients with Swagger

The biggest advantage of generated API clients would save a lot of time from writing Angular services and model manually. Let us see how it can be achieved.


In this tutorial, we will use Swagger Codegen library to generate API clients. The library can be downloaded directly from Maven Repository.

Swagger Codegen 2.x supports OpenAPI Spec 2.0 and 3.x supports both OpenAPI spec 2.0 and 3.0. Since our Swagger spec is compliant to OpenAPI spec 2.0, we will use Swagger Codegen 2.x for generating API client.

Download the jar file from Maven repository. Open the command prompt or terminal and run the following command. If you are on windows, remove the backslashes and write the entire command in single line.

java -jar swagger-codegen-cli-2.4.4.jar generate -i api-docs.json </em>
-l typescript-angular </em>
-o angular-client </em>
— additional-properties [email protected]/language-api,snapshot=true,ngVersion=7.2.0
  • -i or --input-spec defines the location of the input swagger spec, as URL or file (required)
  • -l or --lang defines the client language to generate (required)
  • -o or --output defines the output directory, where the generated files should be written to (current directory by default)
  • -c or --config defines the path to an additional JSON configuration file. Supported options can be different for each language. We will look at this argument in the paragraph.

Note that the — additional-properties is added to generate the client as an Angular package. These properties can also be provided via a config file through -c or — config parameter.

The detailed explanation can be found via the following command.

java -jar swagger-codegen-cli.jar help generate

The generated file structure would be as follows:

Here, the .ng_pkg_build directory and ng-package.json can be ignored for now. They are not generated by Swagger Codegen.

This is a complete Angular project with all required configuration files and typescript files to create an angular (ng) package. I have published the generated Angular package to npm. Let us see how to publish to npm.

Publish Angular Package to NPM

Login to NPM and create a new user account if you don’t have one. In the terminal or command prompt, type npm adduser. Provide your new npm username and password. This is to authorize your machine to publish npm packages.


Run npm install in the above generated npm package. Then, run npm run build command.

We might have to install tsickle module as required by npm packager module. Install the module if necessary.

Finally, run npm publish dist — access=public command to publish to npm repository.

Here, the — access=public makes the repository public. By, default the repository would be set to restricted.

Once it is published, you will find your package under your account as below.

Consuming API Clients with Swagger

Now that we have generated API client and uploaded it to NPM, we will create a small Angular project through Angular CLI and include the generated API client.


Run the following command in terminal to create a new Angular project.

ng new angular7-swagger

Once the project is created, install the following npm module within the project.

npm install @techshard/language-rest-api

Here, language-rest-api is the npm package that contains our generated Angular.

Edit the app.module.ts file and include this npm package. Here is the complete file.

import { BrowserModule } from '@angular/platform-browser';

import { NgModule } from '@angular/core';

import { ApiModule, BASE_PATH } from '@techshard/language-rest-api';

import { HttpClientModule } from '@angular/common/http';

import { environment } from './../environments/environment';

import { AppRoutingModule } from './app-routing.module';

import { AppComponent } from './app.component';

import { LanguageComponent } from './language/language.component';

@NgModule({

declarations: [

AppComponent,

LanguageComponent

],

imports: [

BrowserModule,

AppRoutingModule,

ApiModule,

HttpClientModule

],

providers: [{ provide: BASE_PATH, useValue: environment.API_BASE_PATH }],

bootstrap: [AppComponent]

})

export class AppModule { }

Let’s create a new module “language” and import generated service and data model in this new component. Then, we will call the API to retrieve the list of languages.

import { Component, OnInit } from '@angular/core';

import { LanguageControllerService, Language } from '@techshard/language-rest-api';

@Component({

selector: 'app-language',

templateUrl: './language.component.html',

styleUrls: ['./language.component.scss']

})

export class LanguageComponent implements OnInit {

languages: Language[] = [];

constructor(private languageControllerService: LanguageControllerService) { }

ngOnInit() {

this.languageControllerService.getLanguagesUsingGET().subscribe(res => {

for (var i in res) {

this.languages.push(res[i]);

}

});

}

}

The language.component.html shall be modified as follows to display the list of languages.

<p

*ngFor="let l of languages; index as i" class="panel-body">

<a class="text-success">{{l.name}}</a>

</p>

Now, edit the app.component.html and add the language template. The complete HTML file is here:

<div class="container blue">

<div class="page-header">

<nav class="navbar navbar-inverse">

<div class="container-fluid">

<div class="navbar-header">

<a class="navbar-brand" href="https://l.morioh.com/b0a3f595aa?r=https://en.wikipedia.org/wiki/List_of_programming_languages" rel="nofollow" target="_blank">

List of programming languages</a>

</div>

</div>

</nav>

</div>

<div class="col-md-2">

<app-language></app-language>

</div>

<div class="col-md-9">

<router-outlet></router-outlet>

</div>

</div>

Note that I have installed Bootstrap 4 and imported into the project.

Running the Angular App

Run the following command in the terminal.


ng serve

Once the project is compiled successfully, open http://localhost:4200 in the browser and we will see list of languages as follows:

Conclusion

Congratulations! We have learned to automatically generate and consume API code. This approach reduces errors and writing a lot of boilerplate code. Also, this approach can be very useful for developing Micro Frontends.


The complete source code can be found on my GitHub repository.

I hope you enjoyed my article. Feel free to leave any comments or suggestions.

Happy learning!

---------------------------------------------------------------------------------------------------

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Learn More

☞ The Complete JavaScript Course 2019: Build Real Projects!

☞ Vue JS 2 - The Complete Guide (incl. Vue Router & Vuex)

☞ JavaScript Bootcamp - Build Real World Applications

☞ The Web Developer Bootcamp

☞ JavaScript: Understanding the Weird Parts

☞ Building a Simple URL Shortener With Just HTML and Javascript

☞ The JavaScript Developer’s Guide To Node.JS

☞ From Javascript to Typescript to Elm

☞ Google’s Go Essentials For Node.js / JavaScript Developers

☞ 3 things you didn’t know about the forEach loop in JavaScript



Angular file upload Spring Boot backend

Angular file upload Spring Boot backend

This tutorial shows a step by step solution to Angular file upload Spring Boot backend and store it on file system. We will cover a file upload from a basic input and also from a drag-drop area.

Server side

This section covers the Spring Boot server implementation

Spring Boot Controller

Our Spring Boot application exposes a single endpoint to upload files. Below it’s content :

package com.roufid.tutorials.controller;
 
import com.roufid.tutorials.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
 
import java.io.IOException;
 
@RestController
public class FileController {
 
	private final FileService fileService;
 
	@Autowired
	public FileController(FileService fileService) {
		this.fileService = fileService;
	}
 
	@PostMapping(value = "/api/files")
	@ResponseStatus(HttpStatus.OK)
	public void handleFileUpload(@RequestParam("file") MultipartFile file) throws IOException {
		fileService.storeFile(file);
	}
 
}

The controller is as simple as you see. Let’s see now the  FileService.

Spring Component FileService

We extracted the logic of storing the file a Spring Component FileService. Below it’s content :

package com.roufid.tutorials.service;
 
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
 
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
 
@Component
public class FileService {
 
	private static final String FILE_DIRECTORY = "/var/files";
 
	public void storeFile(MultipartFile file) throws IOException {
		Path filePath = Paths.get(FILE_DIRECTORY + "/" + file.getOriginalFilename());
 
		Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
	}
}

That’s it for server side. Let’s see now the front end implementation.

Client side with Angular

Angular CLI is used to create the Angular application along with  ng2-file-upload module to handle file upload from client side.

Angular Module

First, add the FileUploadModule to app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import {FileUploadModule} from 'ng2-file-upload';
 
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
 
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FileUploadModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Angular component

Our main component contains a input of type “file” and a drag-and-drop area :

  • app.component.html
<h1>Roufid File upload tutorial</h1>
 
<div>
  <input #fileInput type="file" ng2FileSelect [uploader]="uploader"/>
 
  <div class="drop-box" ng2FileDrop
       [ngClass]="{'dragover': isDropOver}"
       [uploader]="uploader"
       (fileOver)="fileOverAnother($event)"
       (click)="fileClicked()">
    <span class="drag-in-title">Import or drag file here</span>
    <span class="drag-over-title">Drop the file</span>
  </div>
</div>
 
<router-outlet></router-outlet>

A CSS is added to make the look and feel pretty

  • app.component.scss
.drop-box {
  cursor: pointer;
  background: #F8F8F8;
  border: 5px dashed #DDD;
  text-align: center;
  padding: 40px;
}
 
.drag-over-title {
  display: none;
}
 
.drop-box.dragover {
  opacity: 0.6;
}
 
.drop-box.dragover .drag-over-title {
  display: block;
}
 
.drop-box.dragover .drag-in-title {
  display: none;
}
 
input[type='file'] {
  display: none;
}

And then creating the logic in the TypeScript component.

  • app.component.ts
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import {FileUploader} from 'ng2-file-upload';
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
 
  @ViewChild('fileInput') fileInput: ElementRef;
 
  uploader: FileUploader;
  isDropOver: boolean;
 
  ngOnInit(): void {
    const headers = [{name: 'Accept', value: 'application/json'}];
    this.uploader = new FileUploader({url: 'api/files', autoUpload: true, headers: headers});
 
    this.uploader.onCompleteAll = () => alert('File uploaded');
  }
 
  fileOverAnother(e: any): void {
    this.isDropOver = e;
  }
 
  fileClicked() {
    this.fileInput.nativeElement.click();
  }
}

That’s is. The result looks like :Angular Spring boot file upload

Conclusion

I hope this article served you that you were looking for. If you have anything that you want to add or share then please share it below in the comment section.

Built Secure API with Spring Boot Angular 6

Built Secure API with Spring Boot Angular 6

✅Spring boot and angular to demonstrate how we can secure your angular application using Basic Authentication via Spring Security. You will learn to use Spring Boot for quickly building web API

1.Spring Boot Angular 6 Example

Today web security is very important, creating and using any services without security is very risky nowadays. If you are transmitting very sensitive data, you must like to use a very nice secure layer to protect your confidential data, Isn’t it?.

It’s all about securing our systems.

So, Whenever we are using API call or anything we must have to first consider the security of our sensitive data. There are so many types of security methods available to secure our data. In this post, We comprehensive example on Spring security, Spring boot and angular to demonstrate how we can secure your angular application using Basic Authentication via Spring Security. You will learn to use Spring Boot for quickly building web API, in short, a java backend layer and then adding Spring Security maven dependency for making it secure. We will use Angular platform that makes it easy to build the application with the web and use our own database.

Before beginning developing the application here is the showcase of the application that we are going to build in the next sections.

Above is an example of my heroes that displays a list of hero names. Initially, we will keep the endpoint non-secure so that the Angular is able to display the list of names. Then, we will add a secure REST endpoint and make the necessary changes to our Angular app for accessing the data using Basic Authentication.

2. Technologies Used

  • Angular 6
  • Spring Boot 2.0.6
  • Java 1.8.0
  • Apache Maven 3.5.3
  • Visual Studio Code 1.28.2
  • Spring Tool Suite 3.9.5
  • Mysql 5.7.19

3. Build Java back-end using Spring Boot

There are many ways we can create a Spring Boot project. The following is a list of ways:

We will not go deeper into each and everyone will just use Spring Tool Suite to create a project.

3.1 Create a starter project

Go to File->New->Spring Starter Project and enter necessary details and select dependencies as per the following screenshots

You will have to select these dependencies in order to add in maven POM.xml file, then just hit the finish button and project will be created with required libraries

3.2 Create a database

Create a table into your database then feeds with data, here is the query for creating and inserting records into the table.

CREATE TABLE `heroes` (
`id` int(3) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
KEY `id` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;
insert  into `heroes`(`id`,`name`) values (1,'Aung San Suu Kyi'),(2,'Nelson Mandela'),(3,'Tony Blair'),(4,'Dr. Abdul Kalam'),(5,'Srinivas Ramanujan'),(6,'Stephen Hawking'),(7,'Bob Geldof'),(8,'Margaret Thatcher'),(9,'Winston Churchill'),(10,'Bob Dylan'),(11,'Bill Gates'),(12,'Steve Jobs'),(13,'Dalai Lama'),(14,'David Attenborough'),(15,'Mary Robinson'),(16,'Tim Berners-Lee');

3.3 Add an insecure REST endpoint

Just add the following code as shown below to each file :

1. Application.properties :

server.port=7070
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.generate-ddl=true

2. SecureRestServiceApplication.java :

package com.example.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication
@EntityScan(basePackages = "com.example.model")
@EnableJpaRepositories(basePackages = {"com.example.repository"})
@ComponentScan(basePackages = "com.example.controller")
public class SecureRestServiceApplication {
public static void main(String[] args) {
SpringApplication.run(SecureRestServiceApplication.class, args);
}
}

3. HeroesController.java

package com.example.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.model.Heroes;
import com.example.repository.HeroesRepository;

@RestController
@RequestMapping("/api/")
public class HeroesController {
	@Autowired
	HeroesRepository heroesRepository;
	
	@RequestMapping("/heroes")	
	@CrossOrigin("http://localhost:4200")
	public List getHeroes(){
		return heroesRepository.findAll();
	}
}

4. Heroes.java

package com.example.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "heroes")
public class Heroes {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private int id;
@Column(name = "name")
private String name;
public Heroes() {}
public Heroes(int id, String name) {
super();
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

5. HeroesRepository.java

package com.example.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import com.example.model.Heroes;
public interface HeroesRepository extends JpaRepository {}

6. Execute in postman or any browser

Now, restart the application, open browser or use postman to hit this URL http://localhost:7070/api/heroes. You should see the information of heroes in JSON format see below :)

4. Build front-end application using Angular

Let’s build a front-end, we will use Angular CLI to create an Angular app. Execute the following command in your command prompt.

ng new secure-hero-app

The command might take few minutes for creating configuration files and bringing in all the dependencies. It will also create a very simple application for you. Once the command completes, execute

ng serve --open

This command will start and will open your default browser automatically with the http://localhost:4200 URL and you will see default angular page.

4.1 Create required artifacts

We now need to create some components, services and routing modules to our angular app. We will create 1 component, 1 routing module and 1 service using these following command

1. Create a service

ng generate service hero
  • Hero.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class HeroService {

  constructor(private http: HttpClient) { }

  getHeroes() {
    const url = 'http://localhost:7070/api/heroes';    
    return this.http.get(url);
  }
}

2. Create a routing module

ng generate module app-routing --flat --module=app

In the app-routing module add the following code.

  • App-routing.module.ts
import { HeroesComponent } from './heroes/heroes.component';
import { NgModule } from '@angular/core';
import { RouterModule, Routes} from '@angular/router';

const routes: Routes = [
  { path: 'heroes', component: HeroesComponent}
];

@NgModule({
  imports: [
    RouterModule.forRoot(routes)
  ],
  exports: [ RouterModule]
})
export class AppRoutingModule { }

3. Create a component

ng generate component heroes

Update your component files as shown below :

/* HeroesComponent's private CSS styles */
  .heroes {
    margin: 0 0 2em 0;
    list-style-type: none;
    padding: 0;
    width: 15em;
  }
  .heroes li {
    cursor: pointer;
    position: relative;
    left: 0;
    background-color: #EEE;
    margin: .5em;
    padding: .3em 0;
    height: 1.6em;
    border-radius: 4px;
  }
  .heroes li.selected:hover {
    background-color: #BBD8DC !important;
    color: white;
  }
  .heroes li:hover {
    color: #607D8B;
    background-color: #DDD;
    left: .1em;
  }
  .heroes .text {
    position: relative;
    top: -3px;
  }
  .heroes .badge {
    display: inline-block;
    font-size: small;
    color: white;
    padding: 0.8em 0.7em 0 0.7em;
    background-color: #607D8B;
    line-height: 1em;
    position: relative;
    left: -1px;
    top: -4px;
    height: 1.8em;
    margin-right: .8em;
    border-radius: 4px 0 0 4px;
  }

heroes.component.css

## My Heroes

{{hero.id}} {{hero.name}} 
 --> {{hero.id}} {{hero.name}} 

**heroes.component.html **

import { HeroService } from './../hero.service';
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {

  heroes$: Object;
  constructor(private data: HeroService) { }

  ngOnInit() {
    this.data.getHeroes().subscribe(
      data => this.heroes$ = data
    );
  }

}

heroes.component.ts

4. A glance at the App module

For using HTTP service in our service class, we need to import HttpClientModule as shown below :

  • App.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { HeroesComponent } from './heroes/heroes.component';
import { AppRoutingModule } from './/app-routing.module';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent,
    HeroesComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

4.2 Access the application

After going through the above steps, you should be able to see the list of heroes by accessing http://localhost:4200/heroes in the browser.

5. Secure your REST endpoint using Spring Security

To enable spring security in your app, simply add the following dependency to the pom.xml


org.springframework.boot
spring-boot-starter-security

Now, try accessing http://localhost:7070/api/heroes . You should see a browser dialogue asking for the credentials as shown below

The default username is user and you can get the password from your STS console as shown Below

In order to use your own username and password instead of generating one, you need to specify the following properties in the application.properties file as shown below.

spring.security.user.name=test
spring.security.user.password=test123

6. Update your angular endpoint for accessing secure REST endpoint

If you try to access http://localhost:4200/heroes in the browser or postman it will throw 401 status code that means unauthorized access to the URL. We need to send basic authorization headers in our HTTP request. So let’s update our hero service, specifying the method getHeroes()

getHeroes() {
const url = 'http://localhost:7070/api/heroes';
const headers = new HttpHeaders({Authorization: 'Basic ' + btoa('test:test123')});
return this.http.get(url, { headers });
}

Now try again by verifying http://localhost:7070/api/heroes browser. Does it work?.

No, and the reason for this is we need to add support of CORS protocol at our REST back-end, It is explained in the next section.

7. Cross-origin resource sharing (CORS)

We need to support the CORS protocol for our Angular service to be able to invoke an endpoint on a different domain. By different domain, we mean that our front-end application running on http://localhost:4200 is requesting a resource on another domain i.e. http://localhost:7070. Hence, on the server, we need to configure the CORS. This is done by providing the support for CORS protocol.

We need to create Configuration class for web security, this class will signal the Spring security to allow pre-flight check from the browser. This is done by overriding the configure method of WebSecurityConfigurerAdapter.

We need to create two files CustomFilter.java and SecurityConfiguration.java as shown below :

package com.example.config;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.filter.OncePerRequestFilter;

public class CustomFilter extends OncePerRequestFilter {

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws ServletException, IOException {
		response.setHeader("Access-Control-Allow-Origin", "*");
		response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
		response.setHeader("Access-Control-Allow-Headers",
				"authorization, content-type, xsrf-token, Cache-Control, remember-me, WWW-Authenticate");
		response.addHeader("Access-Control-Expose-Headers", "xsrf-token");
		chain.doFilter(request, response);
		
	}

}

Now Verify again http://localhost:4200 in the browser and you should be able to see the listing of heroes again.

Congratulations!!!!

8. Short Summary

In this post, we have created a simple REST API using Spring Boot. We secured this using Spring Security Using Basic Authentication type. At last, we used the popular front-end platform Angular for accessing the secure API.

spring boot + angular 4 example, integrate angular 6 with spring boot,

Build Web App Authentication using Spring Boot, Spring Security, MongoDB and Angular 8

Build Web App Authentication using Spring Boot, Spring Security, MongoDB and Angular 8

In this article, you'll learn how to build web application authentication using Spring Boot, Spring Security, MongoDB, and Angular 8

In this tutorial, we have to build Spring Boot, Spring Security Core, and MongoDB RESTful Authentication as the backend. The Angular 8 used as the frontend using the HttpClient, HttpInterceptor, and RouteGuard modules. The secure page guarded by Angular 8 Route Guard and the secure RESTful API guarded by Spring Security REST. This should be a great combination of a secure modern web application.

The flow of the web application looks like below Sequence Diagram.

The Angular 8 application starts with the secure and route guarded page list of products without authorization headers. The Angular 8 redirect to the Login page. After login with the valid credential, the Angular 8 got the JWT token that validates with Route guard also send together with Authorization headers bearer. In the products list page, the Angular 8 application request products API to Spring Boot Security API include authorization headers. Finally, the Angular 8 page displaying the list of products.

The following tools, frameworks, libraries, and modules are required for this tutorial:

We assume that you already installed all required software, tools, and frameworks. So, we will not cover how to install that software, tools, and frameworks. Notes: We are using Spring Boot 2.1.6 (Stable version), JDK 1.8.0_92, and Angular 8.0.6.

Generate a New Spring Boot Gradle Project

To create or generate a new Spring Boot Application or Project, simply go to Spring Initializer. Fill all required fields as below then click on Generate Project button.

The project will automatically be downloaded as a Zip file. Next, extract the zipped project to your java projects folder. On the project folder root, you will find build.gradle file for register dependencies, initially it looks like this.

plugins {

    id 'org.springframework.boot' version '2.1.6.RELEASE'

    id 'java'

    id 'war'

}

 

apply plugin: 'io.spring.dependency-management'

 

group = 'com.djamware'

version = '0.0.1-SNAPSHOT'

sourceCompatibility = '1.8'

 

repositories {

    mavenCentral()

}

 

dependencies {

    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'

    implementation 'org.springframework.boot:spring-boot-starter-security'

    implementation 'org.springframework.boot:spring-boot-starter-web'

    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    testImplementation 'org.springframework.security:spring-security-test'

}

Now, you can work with the source code of this Spring Boot Project using your own IDE or Text Editor. We are using Visual Studio Code (VSCode). In the terminal or command line go to the extracted project folder then run the command to open VSCode.

cd spring-angular-auth 
code .

Next, we have to add the JWT library to the build.gradle as the dependency. Open and edit build.gradle then add this line to dependencies after other implementation.

implementation 'io.jsonwebtoken:jjwt:0.9.1'

Next, compile the Gradle Project by type this command from Terminal or CMD.

gradle compileJava

Next, open and edit src/main/resources/application.properties then add these lines.

spring.data.mongodb.database=springmongodb 
spring.data.mongodb.host=localhost 
spring.data.mongodb.port=27017

Create Product, User and Role Model or Entity Classes

We will be creating all required models or entities for products, user and role. Create a new folder and files inside that new folder.

mkdir src/main/java/com/djamware/springangularauth/models touch src/main/java/com/djamware/springangularauth/models/Products.java touch src/main/java/com/djamware/springangularauth/models/User.java touch src/main/java/com/djamware/springangularauth/models/Role.java

Next, open and edit src/main/java/com/djamware/springangularauth/models/Products.java then add these package and imports.

package com.djamware.springangularauth.models; 

import org.springframework.data.annotation.Id; 
import org.springframework.data.mongodb.core.mapping.Document;

Next, add these lines of codes for class, fields, document annotation that map to MongoDB collection, constructors, and getter/setter for each field.

@Document(collection = "products")
public class Products {
 
    @Id
    String id;
    String prodName;
    String prodDesc;
    Double prodPrice;
    String prodImage;
 
    public Products() {
    }
 
    public Products(String prodName, String prodDesc, Double prodPrice, String prodImage) {
        super();
        this.prodName = prodName;
        this.prodDesc = prodDesc;
        this.prodPrice = prodPrice;
        this.prodImage = prodImage;
    }
 
    public String getId() {
        return id;
    }
 
    public void setId(String id) {
        this.id = id;
    }
 
    public String getProdName() {
        return prodName;
    }
 
    public void setProdName(String prodName) {
        this.prodName = prodName;
    }
 
    public String getProdDesc() {
        return prodDesc;
    }
 
    public void setProdDesc(String prodDesc) {
        this.prodDesc = prodDesc;
    }
 
    public Double getProdPrice() {
        return prodPrice;
    }
 
    public void setProdPrice(Double prodPrice) {
        this.prodPrice = prodPrice;
    }
 
    public String getProdImage() {
        return prodImage;
    }
 
    public void setProdImage(String prodImage) {
        this.prodImage = prodImage;
    }
 
}

Next, open and edit src/main/java/com/djamware/springangularauth/models/User.java then add these package and imports.

package com.djamware.springangularauth.models;
 
import java.util.Set;
 
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.IndexDirection;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;

Add these lines of Java codes that contain document annotation that map to the MongoDB collection, class name, fields, constructors, and getter/setter of the fields.

@Document(collection = "users")
public class User {
 
    @Id
    private String id;
    @Indexed(unique = true, direction = IndexDirection.DESCENDING, dropDups = true)
    private String email;
    private String password;
    private String fullname;
    private boolean enabled;
    @DBRef
    private Set<Role> roles;
 
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    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 getFullname() {
        return fullname;
    }
    public void setFullname(String fullname) {
        this.fullname = fullname;
    }
    public boolean isEnabled() {
        return enabled;
    }
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }
    public Set<Role> getRoles() {
        return roles;
    }
    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }
 
}

Next, open and edit src/main/java/com/djamware/springangularauth/models/Role.java then add these package name and imports.

package com.djamware.springangularauth.models;
 
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.IndexDirection;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;

Add these lines of codes that contain document annotation that map to MongoDB collection, class name, fields/variables, constructors, and getter/setter for each field/variables.

@Document(collection = "users")
public class User {
 
    @Id
    private String id;
    @Indexed(unique = true, direction = IndexDirection.DESCENDING, dropDups = true)
    private String email;
    private String password;
    private String fullname;
    private boolean enabled;
    @DBRef
    private Set<Role> roles;
 
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    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 getFullname() {
        return fullname;
    }
    public void setFullname(String fullname) {
        this.fullname = fullname;
    }
    public boolean isEnabled() {
        return enabled;
    }
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }
    public Set<Role> getRoles() {
        return roles;
    }
    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }
 
}

If you want easier Java class creation, auto import organization, generate constructor, and generate getter/setter then you can use IDE like Eclipse, Netbeans, Spring Tool-Suite, or IntelliJ IDEA. So, you don't have to type all codes manually.

Create Product, User and Role Repository Interfaces

Next steps to create Product, User, and Role Repository Interfaces. Create a new folder and files for the repositories.

mkdir src/main/java/com/djamware/springangularauth/repositories
touch src/main/java/com/djamware/springangularauth/repositories/ProductRepository.java
touch src/main/java/com/djamware/springangularauth/repositories/UserRepository.java
touch src/main/java/com/djamware/springangularauth/repositories/RoleRepository.java

Next, open and edit src/main/java/com/djamware/springangularauth/repositories/ProductRepository.java then add these package name and imports.

package com.djamware.springangularauth.repositories;
 
import com.djamware.springangularauth.models.Products;
import org.springframework.data.mongodb.repository.MongoRepository;

Add these lines of codes that contain Java interface name that extends MongoRepository and deletes method.

public interface ProductRepository extends MongoRepository<Products, String> {
 
    @Override
    void delete(Products deleted);
}

Next, open and edit src/main/java/com/djamware/springangularauth/repositories/UserRepository.java then add these package name and imports.

package com.djamware.springangularauth.repositories; 

import com.djamware.springangularauth.models.User; 
import org.springframework.data.mongodb.repository.MongoRepository;

Add these lines of codes that contain Java interface name that extends MongoRepository and find by email method.

public interface UserRepository extends MongoRepository<User, String> {
 
    User findByEmail(String email);
}

Next, open and edit src/main/java/com/djamware/springangularauth/repositories/RoleRepository.java then add these package name and imports.

package com.djamware.springangularauth.repositories; 

import org.springframework.data.mongodb.repository.MongoRepository; 
import com.djamware.springangularauth.models.Role;

Add these lines of codes that contain Java interface name that extends MongoRepository and find by role method.

public interface RoleRepository extends MongoRepository<Role, String> {
 
    Role findByRole(String role);
}

Create a Custom User Details Service

To implements authentication using existing User and Role from MongoDB, we have to create a custom user details service. Create a new folder and Java file for the service.

mkdir src/main/java/com/djamware/springangularauth/services 
touch src/main/java/com/djamware/springangularauth/services/CustomUserDetailsService.java

Next, open and edit src/main/java/com/djamware/springangularauth/services/CustomUserDetailsService.java then add these package name and imports.

package com.djamware.springangularauth.services;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.password.PasswordEncoder;
import org.springframework.stereotype.Service;
 
import com.djamware.springangularauth.models.Role;
import com.djamware.springangularauth.models.User;
import com.djamware.springangularauth.repositories.RoleRepository;
import com.djamware.springangularauth.repositories.UserRepository;

Add these lines of codes that contains service annotation, the class name that implements UserDetailsService, required variables, constructors, and methods for the authentication process.

@Service
public class CustomUserDetailsService implements UserDetailsService {
 
    @Autowired
    private UserRepository userRepository;
 
    @Autowired
    private RoleRepository roleRepository;
 
    @Autowired
    private PasswordEncoder bCryptPasswordEncoder;
 
    public User findUserByEmail(String email) {
        return userRepository.findByEmail(email);
    }
 
    public void saveUser(User user) {
        user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
        user.setEnabled(true);
        Role userRole = roleRepository.findByRole("ADMIN");
        user.setRoles(new HashSet<>(Arrays.asList(userRole)));
        userRepository.save(user);
    }
 
    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
 
        User user = userRepository.findByEmail(email);
        if(user != null) {
            List<GrantedAuthority> authorities = getUserAuthority(user.getRoles());
            return buildUserForAuthentication(user, authorities);
        } else {
            throw new UsernameNotFoundException("username not found");
        }
    }
 
    private List<GrantedAuthority> getUserAuthority(Set<Role> userRoles) {
        Set<GrantedAuthority> roles = new HashSet<>();
        userRoles.forEach((role) -> {
            roles.add(new SimpleGrantedAuthority(role.getRole()));
        });
 
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>(roles);
        return grantedAuthorities;
    }
 
    private UserDetails buildUserForAuthentication(User user, List<GrantedAuthority> authorities) {
        return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), authorities);
    }
}

Configure Spring Boot Security Rest

Now, the main purpose of this tutorial is configuring Spring Security Rest. First, we have to create a bean for JWT token generation and validation. Create a new folder and file for the configuration.

mkdir src/main/java/com/djamware/springangularauth/configs
touch src/main/java/com/djamware/springangularauth/configs/JwtTokenProvider.java
touch src/main/java/com/djamware/springangularauth/configs/JwtConfigurer.java
touch src/main/java/com/djamware/springangularauth/configs/JwtTokenFilter.java
touch src/main/java/com/djamware/springangularauth/configs/WebSecurityConfig.java

Next, open and edit src/main/java/com/djamware/springangularauth/configs/JwtTokenProvider.java then add these package name and imports.

package com.djamware.springangularauth.configs;
 
import java.util.Base64;
import java.util.Date;
import java.util.Set;
 
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
 
import com.djamware.springangularauth.models.Role;
import com.djamware.springangularauth.services.CustomUserDetailsService;
 
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

Next, add these lines of codes that contain component annotation, class name, variables, and methods.

@Component
public class JwtTokenProvider {
 
      @Value("${security.jwt.token.secret-key:secret}")
    private String secretKey = "secret";
 
    @Value("${security.jwt.token.expire-length:3600000}")
    private long validityInMilliseconds = 3600000; // 1h
 
    @Autowired
    private CustomUserDetailsService userDetailsService;
 
    @PostConstruct
    protected void init() {
        secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
    }
 
    public String createToken(String username, Set<Role> set) {
        Claims claims = Jwts.claims().setSubject(username);
        claims.put("roles", set);
        Date now = new Date();
        Date validity = new Date(now.getTime() + validityInMilliseconds);
        return Jwts.builder()//
            .setClaims(claims)//
            .setIssuedAt(now)//
            .setExpiration(validity)//
            .signWith(SignatureAlgorithm.HS256, secretKey)//
            .compact();
    }
 
    public Authentication getAuthentication(String token) {
        UserDetails userDetails = this.userDetailsService.loadUserByUsername(getUsername(token));
        return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
    }
 
    public String getUsername(String token) {
        return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
    }
 
    public String resolveToken(HttpServletRequest req) {
        String bearerToken = req.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7, bearerToken.length());
        }
        return null;
    }
 
    public boolean validateToken(String token) {
        try {
            Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
            if (claims.getBody().getExpiration().before(new Date())) {
                return false;
            }
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            throw new JwtException("Expired or invalid JWT token");
        }
    }
}

Next, open and edit src/main/java/com/djamware/springangularauth/configs/JwtTokenFilter.java then add these package name and imports.

package com.djamware.springangularauth.configs;
 
import java.io.IOException;
 
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
 
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

Next, add these lines of codes that contain component annotation, class name, variables, and methods.

public class JwtTokenFilter extends GenericFilterBean {
 
    private JwtTokenProvider jwtTokenProvider;
 
    public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }
 
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
        throws IOException, ServletException {
        String token = jwtTokenProvider.resolveToken((HttpServletRequest) req);
        if (token != null && jwtTokenProvider.validateToken(token)) {
            Authentication auth = token != null ? jwtTokenProvider.getAuthentication(token) : null;
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        filterChain.doFilter(req, res);
    }
}

Next, open and edit src/main/java/com/djamware/springangularauth/configs/JwtConfigurer.java then add these package name and imports.

package com.djamware.springangularauth.configs;
 
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

Next, add these lines of codes that contain component annotation, the class name that extends the SecurityConfigurerAdapter, variables, and methods.

public class JwtConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
 
    private JwtTokenProvider jwtTokenProvider;
 
    public JwtConfigurer(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }
 
    @Override
    public void configure(HttpSecurity http) throws Exception {
        JwtTokenFilter customFilter = new JwtTokenFilter(jwtTokenProvider);
        http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

Finally, we have to configure the Spring Security by open and edit src/main/java/com/djamware/springangularauth/configs/WebSecurityConfig.java then add these package name and imports.

package com.djamware.springangularauth.configs;
 
import javax.servlet.http.HttpServletResponse;
 
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
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.AuthenticationEntryPoint;
 
import com.djamware.springangularauth.services.CustomUserDetailsService;

Add these lines of codes that contains Configuration and EnableWebSecurity annotation, class name that extends WebSecurityConfigurerAdapter, required variables, constructors, and methods.

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Autowired
    JwtTokenProvider jwtTokenProvider;
 
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        UserDetailsService userDetailsService = mongoUserDetails();
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
 
    }
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic().disable().csrf().disable().sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
                .antMatchers("/api/auth/login").permitAll().antMatchers("/api/auth/register").permitAll()
                .antMatchers("/api/products/**").hasAuthority("ADMIN").anyRequest().authenticated().and().csrf()
                .disable().exceptionHandling().authenticationEntryPoint(unauthorizedEntryPoint()).and()
                .apply(new JwtConfigurer(jwtTokenProvider));
    http.cors();
    }
 
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**");
    }
 
    @Bean
    public PasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
 
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
 
    @Bean
    public AuthenticationEntryPoint unauthorizedEntryPoint() {
        return (request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
                "Unauthorized");
    }
 
    @Bean
    public UserDetailsService mongoUserDetails() {
        return new CustomUserDetailsService();
    }
}

Create Product and Authentication Controllers

Now it's time for REST API endpoint. All RESTful API will be created from each controller. Product controller will handle CRUD endpoint of product and Authentication controller will handle login and register endpoint. Create a new folder and files for the controllers.

mkdir src/main/java/com/djamware/springangularauth/controllers
touch src/main/java/com/djamware/springangularauth/controllers/ProductController.java
touch src/main/java/com/djamware/springangularauth/controllers/AuthController.java
touch src/main/java/com/djamware/springangularauth/controllers/AuthBody.java

Next, open and edit src/main/java/com/djamware/springangularauth/controllers/AuthController.java then add these package name and imports.

package com.djamware.springangularauth.controllers;
 
import java.util.Optional;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
import com.djamware.springangularauth.models.Products;
import com.djamware.springangularauth.repositories.ProductRepository;

Add these lines of codes that contain RestController annotation, class name, required variables, constructors, and methods.

@CrossOrigin(origins = "*")
@RequestMapping("/api")
@RestController
public class ProductController {
 
    @Autowired
    ProductRepository productRepository;
 
    @RequestMapping(method = RequestMethod.GET, value = "/products")
    public Iterable<Products> product() {
        return productRepository.findAll();
    }
}

Next, open and edit src/main/java/com/djamware/springangularauth/controllers/AuthController.java then add these package name and imports.

package com.djamware.springangularauth.controllers;
 
import static org.springframework.http.ResponseEntity.ok;
 
import java.util.HashMap;
import java.util.Map;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import com.djamware.springangularauth.configs.JwtTokenProvider;
import com.djamware.springangularauth.models.User;
import com.djamware.springangularauth.repositories.UserRepository;
import com.djamware.springangularauth.services.CustomUserDetailsService;

Add these lines of codes that contain RestController and RequestMapping annotation, class name, required variables, constructors, and methods.

@CrossOrigin(origins = "*")
@RestController
@RequestMapping("/api/auth")
public class AuthController {
 
    @Autowired
    AuthenticationManager authenticationManager;
 
    @Autowired
    JwtTokenProvider jwtTokenProvider;
 
    @Autowired
    UserRepository users;
 
    @Autowired
    private CustomUserDetailsService userService;
 
    @SuppressWarnings("rawtypes")
    @PostMapping("/login")
    public ResponseEntity login(@RequestBody AuthBody data) {
        try {
            String username = data.getEmail();
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, data.getPassword()));
            String token = jwtTokenProvider.createToken(username, this.users.findByEmail(username).getRoles());
            Map<Object, Object> model = new HashMap<>();
            model.put("username", username);
            model.put("token", token);
            return ok(model);
        } catch (AuthenticationException e) {
            throw new BadCredentialsException("Invalid email/password supplied");
        }
    }
 
    @SuppressWarnings("rawtypes")
    @PostMapping("/register")
    public ResponseEntity register(@RequestBody User user) {
        User userExists = userService.findUserByEmail(user.getEmail());
        if (userExists != null) {
            throw new BadCredentialsException("User with username: " + user.getEmail() + " already exists");
        }
        userService.saveUser(user);
        Map<Object, Object> model = new HashMap<>();
        model.put("message", "User registered successfully");
        return ok(model);
    }
}

Next, open and edit src/main/java/com/djamware/springangularauth/controllers/AuthBody.java then add these package name.

package com.djamware.springangularauth.controllers;

Add these lines of codes that contains the class name, fields or variables, and getter/setter for those variables.

public class AuthBody {
 
    private String email;
    private String password;
 
    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;
    }
 
}

Install or Update Angular 8 CLI and Create Application

Before installing the Angular 8 CLI, make sure you have installed Node.js https://nodejs.org and can open Node.js command prompt. Next, open the Node.js command prompt then type this command to install

Angular 8 CLI.

sudo npm install -g @angular/cli

Next, create an Angular 8 application by typing this command in the root of the Spring Boot application/project directory.

ng new client

Where client is the name of the Angular 8 application. You can specify your own name, we like to name it client because it's put inside Spring Boot Project directory. If there's a question, we fill them with Y and SCSS. Next, go to the newly created Angular 8 application.

cd client

Run the Angular 8 application for the first time.

ng serve

Now, go to localhost:4200 and you should see this page.

Add Routes for Navigation between Angular 8 Pages/Component

On the previous steps, we have to add Angular 8 Routes when answering the questions. Now, we just added the required pages for CRUD (Create, Read, Update, Delete) Supplier data. Type this commands to add the Angular 8 components or pages.

ng g component products
ng g component auth/login
ng g component auth/register

Open src/app/app.module.ts then you will see those components imported and declared in @NgModule declarations. Next, open and edit src/app/app-routing.module.ts then add these imports.

import { ProductsComponent } from './products/products.component';
import { LoginComponent } from './auth/login/login.component';
import { RegisterComponent } from './auth/register/register.component';

Add these arrays to the existing routes constant.

const routes: Routes = [
  {
    path: '',
    redirectTo: 'products',
    pathMatch: 'full'
  },
  {
    path: 'products',
    component: ProductsComponent,
    data: { title: 'List of Products' }
  },
  {
    path: 'login',
    component: LoginComponent,
    data: { title: 'Login' }
  },
  {
    path: 'register',
    component: RegisterComponent,
    data: { title: 'Register' }
  }
];

Open and edit src/app/app.component.html and you will see the existing router outlet. Next, modify this HTML page to fit the CRUD page.

<div class="container">
  <router-outlet></router-outlet>
</div>

Open and edit src/app/app.component.scss then replace all SCSS codes with this.

.container {
  padding: 20px;
}

Create a custom Angular 8 HttpInterceptor

Before creating a custom Angular 8 HttpInterceptor, create a folder with the name client/src/app/interceptors. Next, create a file for the custom Angular 8 HttpInterceptor with the name client/src/app/interceptors/token.interceptor.ts. Open and edit that file the add these imports.

import { Injectable } from '@angular/core';
import {
    HttpRequest,
    HttpHandler,
    HttpEvent,
    HttpInterceptor,
    HttpResponse,
    HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { Router } from '@angular/router';

Create a class that implementing HttpInterceptor method.

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
 
}

Inject the required module to the constructor inside the class.

constructor(private router: Router) {}

Implement a custom Interceptor function.

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
 
    const token = localStorage.getItem('token');
    if (token) {
      request = request.clone({
        setHeaders: {
          'Authorization': 'Bearer ' + token
        }
      });
    }
    if (!request.headers.has('Content-Type')) {
      request = request.clone({
        setHeaders: {
          'content-type': 'application/json'
        }
      });
    }
    request = request.clone({
      headers: request.headers.set('Accept', 'application/json')
    });
    return next.handle(request).pipe(
      map((event: HttpEvent<any>) => {
        if (event instanceof HttpResponse) {
          console.log('event--->>>', event);
        }
        return event;
      }),
      catchError((error: HttpErrorResponse) => {
        console.log(error);
        if (error.status === 401) {
          this.router.navigate(['login']);
        }
        if (error.status === 400) {
          alert(error.error);
        }
        return throwError(error);
      }));
}

Next, we have to register this custom HttpInterceptor and HttpClientModule. Open and edit client/src/app.module.ts then add these imports.

import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; 
import { TokenInterceptor } from './interceptors/token.interceptor';

Add HttpClientModule to the @NgModule imports array.

imports: [
  BrowserModule,
  AppRoutingModule,
  HttpClientModule
],

Add the Interceptor modules to the provider array of the @NgModule.

providers: [
  {
    provide: HTTP_INTERCEPTORS,
    useClass: TokenInterceptor,
    multi: true
  }
],

Now, the HTTP interceptor is ready to intercept any request to the API.

Create Services for Accessing Product and Authentication API

To access the Spring Boot RESTful API from Angular 8 application, we have to create services for that. Type these commands to generate the Angular 8 services from the client folder.

ng g service auth 
ng g service product

Next, open and edit client/src/app/auth.service.ts then add these imports.

import { HttpClient } from '@angular/common/http'; 
import { Observable, of } from 'rxjs'; 
import { catchError, tap } from 'rxjs/operators';

Declare a constant variable as Spring Boot RESTful API URL after the imports.

const apiUrl = 'http://192.168.0.7:8080/api/auth/';

Declare the variables before the constructor that will use by Angular 8 Route Guard.

isLoggedIn = false; 
redirectUrl: string;

Inject the HttpClient module inside the constructor.

constructor(private http: HttpClient) { }

Create all required functions for Login, Logout, Register, and helper functions.

login(data: any): Observable<any> {
  return this.http.post<any>(apiUrl + 'login', data)
    .pipe(
      tap(_ => this.isLoggedIn = true),
      catchError(this.handleError('login', []))
    );
}
 
logout(): Observable<any> {
  return this.http.get<any>(apiUrl + 'signout')
    .pipe(
      tap(_ => this.isLoggedIn = false),
      catchError(this.handleError('logout', []))
    );
}
 
register(data: any): Observable<any> {
  return this.http.post<any>(apiUrl + 'register', data)
    .pipe(
      tap(_ => this.log('login')),
      catchError(this.handleError('login', []))
    );
}
 
private handleError<T>(operation = 'operation', result?: T) {
  return (error: any): Observable<T> => {
 
    console.error(error); // log to console instead
    this.log(`${operation} failed: ${error.message}`);
 
    return of(result as T);
  };
}
 
private log(message: string) {
  console.log(message);
}

Next, create an object class that represents Product data client/src/app/products/product.ts then replace all file contents with these.

export class Product {
    productId: number;
    isbn: string;
    title: string;
    author: string;
    description: string;
    publisher: string;
    publishedYear: number;
    price: number;
}

Next, open and edit client/src/app/services/product.service.ts then replace all codes with this.

import { Injectable } from '@angular/core';
import { Product } from './products/product';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
 
const apiUrl = 'http://192.168.0.7:8080/api/products';
 
@Injectable({
  providedIn: 'root'
})
export class ProductService {
 
  constructor(private http: HttpClient) { }
 
  getProducts(): Observable<Product[]> {
    return this.http.get<Product[]>(apiUrl + 'Product')
      .pipe(
        tap(_ => this.log('fetched Products')),
        catchError(this.handleError('getProducts', []))
      );
  }
 
  private handleError<T>(operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {
 
      console.error(error); // log to console instead
      this.log(`${operation} failed: ${error.message}`);
 
      return of(result as T);
    };
  }
 
  private log(message: string) {
    console.log(message);
  }
}

Display List of Product using Angular 8 Material

To display a list of products to the Angular 8 template. First, open and edit client/src/app/products/products.component.ts then add these imports.

import { Product } from './product'; 
import { ProductService } from '../product.service'; 
import { AuthService } from '../auth.service'; 
import { Router } from '@angular/router';

Next, inject the Product and Auth Services to the constructor.

constructor(private productService: ProductService, private authService: AuthService, private router: Router) { }

Declare these variables before the constructor.

data: Product[] = [];
displayedColumns: string[] = ['prodName', 'prodDesc', 'prodPrice'];
isLoadingResults = true;

Create a function for consuming or get a product list from the producing service.

getProducts(): void {
  this.productService.getProducts()
    .subscribe(products => {
      this.data = products;
      console.log(this.data);
      this.isLoadingResults = false;
    }, err => {
      console.log(err);
      this.isLoadingResults = false;
    });
}

Call this function from ngOnInit.

ngOnInit() {
  this.getProducts();
}

Add a function for log out the current session.

logout() {
  localStorage.removeItem('token');
  this.router.navigate(['login']);
}

Next, for the user interface (UI) we will use Angular 8 Material and CDK. There's a CLI for generating a Material component like Table as a component, but we will create or add the Table component from scratch to existing component. Type this command to install Angular 8 Material.

ng add @angular/material

If there are some questions, answer them like below.

? Choose a prebuilt theme name, or "custom" for a custom theme: Purple/Green       [ Preview: https://material.angular.i
o?theme=purple-green ]
? Set up HammerJS for gesture recognition? Yes
? Set up browser animations for Angular Material? Yes

Next, we have to register all required Angular Material components or modules to src/app/app.module.ts. Open and edit that file then add this imports.

import {
  MatInputModule,
  MatPaginatorModule,
  MatProgressSpinnerModule,
  MatSortModule,
  MatTableModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule } from '@angular/material';

Also, add FormsModule and ReactiveFormsModule.

import { FormsModule, ReactiveFormsModule } from '@angular/forms';

Register the above modules to @NgModule imports.

imports: [
  BrowserModule,
  FormsModule,
  HttpClientModule,
  AppRoutingModule,
  ReactiveFormsModule,
  BrowserAnimationsModule,
  MatInputModule,
  MatTableModule,
  MatPaginatorModule,
  MatSortModule,
  MatProgressSpinnerModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule
],

Next, open and edit client/src/app/products/products.component.html then replace all HTML tags with this Angular 8 Material tags.

<div class="example-container mat-elevation-z8">
  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <div class="button-row">
    <a mat-flat-button color="primary" (click)="logout()">Logout</a>
  </div>
  <div class="mat-elevation-z8">
    <table mat-table [dataSource]="data" class="example-table">
 
      <!-- Product ID Column -->
      <ng-container matColumnDef="prodName">
        <th mat-header-cell *matHeaderCellDef>Product Name</th>
        <td mat-cell *matCellDef="let row">{{row.prodName}}</td>
      </ng-container>
 
      <!-- ISBN Column -->
      <ng-container matColumnDef="prodDesc">
        <th mat-header-cell *matHeaderCellDef>Product Description</th>
        <td mat-cell *matCellDef="let row">{{row.prodDesc}}</td>
      </ng-container>
 
      <!-- Title Column -->
      <ng-container matColumnDef="prodPrice">
        <th mat-header-cell *matHeaderCellDef>Product Price</th>
        <td mat-cell *matCellDef="let row">{{row.prodPrice}}</td>
      </ng-container>
 
      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
      <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
    </table>
  </div>
</div>

Finally, we have to align the style for this page. Open and edit client/src/app/products/products.component.scss then replace all SCSS codes with these.

/* Structure */
.example-container {
    position: relative;
    padding: 5px;
}
 
.example-table-container {
    position: relative;
    max-height: 400px;
    overflow: auto;
}
 
table {
    width: 100%;
}
 
.example-loading-shade {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 56px;
    right: 0;
    background: rgba(0, 0, 0, 0.15);
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
}
 
.example-rate-limit-reached {
    color: #980000;
    max-width: 360px;
    text-align: center;
}
 
/* Column Widths */
.mat-column-number,
.mat-column-state {
    max-width: 64px;
}
 
.mat-column-created {
    max-width: 124px;
}
 
.mat-flat-button {
    margin: 5px;
}

Create the Angular 8 Login and Register Page

This time for authentication part. Open and edit client/src/app/auth/login/login.component.ts then add these imports.

import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { AuthService } from '../../auth.service';
import { Router } from '@angular/router';
import { ErrorStateMatcher } from '@angular/material/core';

Declare these variables before the constructor.

loginForm: FormGroup;
email = '';
password = '';
matcher = new MyErrorStateMatcher();
isLoadingResults = false;

Inject the imported modules to the constructor.

constructor(private formBuilder: FormBuilder, private router: Router, private authService: AuthService) { }

Initialize NgForm to the NgOnInit function.

ngOnInit() {
  this.loginForm = this.formBuilder.group({
    'email' : [null, Validators.required],
    'password' : [null, Validators.required]
  });
}

Add a function to submit the login form.

onFormSubmit(form: NgForm) {
  this.authService.login(form)
    .subscribe(res => {
      console.log(res);
      if (res.token) {
        localStorage.setItem('token', res.token);
        this.router.navigate(['products']);
      }
    }, (err) => {
      console.log(err);
    });
}

Add a function to go to the Register page.

register() {
  this.router.navigate(['register']);
}

Add a class that handles the form validation above this class.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Next, open and edit client/src/app/auth/login/login.component.html then replace all HTML tags with these.

<div class="example-container mat-elevation-z8">
  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <mat-card class="example-card">
    <form [formGroup]="loginForm" (ngSubmit)="onFormSubmit(loginForm.value)">
      <mat-form-field class="example-full-width">
        <input matInput type="email" placeholder="Email" formControlName="email"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!loginForm.get('email').valid && loginForm.get('email').touched">Please enter your email</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput type="password" placeholder="Password" formControlName="password"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!loginForm.get('password').valid && loginForm.get('password').touched">Please enter your password</span>
        </mat-error>
      </mat-form-field>
      <div class="button-row">
        <button type="submit" [disabled]="!loginForm.valid" mat-flat-button color="primary">Login</button>
      </div>
      <div class="button-row">
        <button type="button" mat-flat-button color="primary" (click)="register()">Register</button>
      </div>
    </form>
  </mat-card>
</div>

Next, give this page a style by open and edit client/src/app/auth/login/login.component.scss then applies these styles codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}
 
.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}
 
.example-full-width {
  width: 100%;
}
 
.example-full-width:nth-last-child() {
  margin-bottom: 10px;
}
 
.button-row {
  margin: 10px 0;
}
 
.mat-flat-button {
  margin: 5px;
}

Next, for register page, open and edit client/src/app/auth/register/register.component.ts then replace all Typescript codes with these.

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { AuthService } from '../../auth.service';
import { Router } from '@angular/router';
import { ErrorStateMatcher } from '@angular/material/core';
 
@Component({
  selector: 'app-register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.scss']
})
export class RegisterComponent implements OnInit {
 
  registerForm: FormGroup;
  fullName = '';
  email = '';
  password = '';
  isLoadingResults = false;
  matcher = new MyErrorStateMatcher();
 
  constructor(private formBuilder: FormBuilder, private router: Router, private authService: AuthService) { }
 
  ngOnInit() {
    this.registerForm = this.formBuilder.group({
      'fullName' : [null, Validators.required],
      'email' : [null, Validators.required],
      'password' : [null, Validators.required]
    });
  }
 
  onFormSubmit(form: NgForm) {
    this.authService.register(form)
      .subscribe(res => {
        this.router.navigate(['login']);
      }, (err) => {
        console.log(err);
        alert(err.error);
      });
  }
 
}
 
/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Next, open and edit client/src/app/auth/register/register.component.html then replace all HTML tags with these.

<div class="example-container mat-elevation-z8">
  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <mat-card class="example-card">
    <form [formGroup]="registerForm" (ngSubmit)="onFormSubmit(registerForm.value)">
      <mat-form-field class="example-full-width">
        <input matInput type="fullName" placeholder="Full Name" formControlName="fullName"
                [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!registerForm.get('fullName').valid && registerForm.get('fullName').touched">Please enter your Full Name</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput type="email" placeholder="Email" formControlName="email"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!registerForm.get('email').valid && registerForm.get('email').touched">Please enter your email</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput type="password" placeholder="Password" formControlName="password"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!registerForm.get('password').valid && registerForm.get('password').touched">Please enter your password</span>
        </mat-error>
      </mat-form-field>
      <div class="button-row">
        <button type="submit" [disabled]="!registerForm.valid" mat-flat-button color="primary">Register</button>
      </div>
    </form>
  </mat-card>
</div>

Finally, open and edit client/src/app/auth/register/register.component.scss then replace all SCSS codes with these.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}
 
.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}
 
.example-full-width {
  width: 100%;
}
 
.example-full-width:nth-last-child() {
  margin-bottom: 10px;
}
 
.button-row {
  margin: 10px 0;
}
 
.mat-flat-button {
  margin: 5px;
}

Secure the Guarded Products Page using Angular 8 Route Guard

As we mention in the beginning that the Angular 8 application will use Angular 8 Route Guard to secure the products page. So, we have both securities for the Angular 8 page and for Spring Boot RESTful API. Type this command to generate a guard configuration file.

ng generate guard auth/auth

Open and edit that file then add this Angular 8 or Typescript imports.

import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from '../auth.service';

Next, add this implements code to the Class name.

export class AuthGuard implements CanActivate

Inject the AuthService and the Router to the constructor params.

constructor(private authService: AuthService, private router: Router) {}

Add the function for the Route Guard.

canActivate(
  next: ActivatedRouteSnapshot,
  state: RouterStateSnapshot): boolean {
  const url: string = state.url;
 
  return this.checkLogin(url);
}

Add the function to check the login status and redirect to the login page if it's not logged in and redirect to the Guarded page if it's logged in.

checkLogin(url: string): boolean {
  if (this.authService.isLoggedIn) { return true; }
 
  // Store the attempted URL for redirecting
  this.authService.redirectUrl = url;
 
  // Navigate to the login page with extras
  this.router.navigate(['/login']);
  return false;
}

Next, open and edit src/app/app-routing.module.ts then add this import.

import { AuthGuard } from './auth/auth.guard';

Modify the product path, so it will look like this.

const routes: Routes = [
  {
    path: 'products',
    canActivate: [AuthGuard],
    component: ProductsComponent,
    data: { title: 'List of Products' }
  },
  {
    path: 'login',
    component: LoginComponent,
    data: { title: 'Login' }
  },
  {
    path: 'register',
    component: RegisterComponent,
    data: { title: 'Register' }
  }
];

Run and Test The Authentication of The Spring Boot, Security, MongoDB, and Angular 8 Web Application

Before run the Spring Boot RESTful API, make sure the MongoDB server is running by type this command in another terminal or command line tab.

mongod

In the different tab run the Spring Boot RESTful API using this command.

gradle bootRun

Open again a new terminal tab then go to the client folder then run the Angular 8 application.

ng serve

Next, open the browser then go to [http://localhost:4200](http://localhost:4200 "http://localhost:4200") and you

should see then login page because of the landing page that point to

products page will redirect to login if not logged in.

That it's the Spring Boot, Security, MongoDB, Angular 8: Build Authentication. You can find the full source code from our GitHub.