Angular JWT Authorization

Angular JWT Authorization

Securing a Single Page Application is a very important part of its implementation, yet sometimes it brings a lot of confusion, especially when there are many ways to achieve it. In this article, I will focus on the approach utilizing JSON Web Tokens (JWT) as a mechanism to convey user rights. Moreover, I will present the benefits and potential pitfalls of JWT-based security.

Securing a Single Page Application is a very important part of its implementation, yet sometimes it brings a lot of confusion, especially when there are many ways to achieve it. In this article, I will focus on the approach utilizing JSON Web Tokens (JWT) as a mechanism to convey user rights. Moreover, I will present the benefits and potential pitfalls of JWT-based security.

In this article, you will learn:

  • how to restrict access to the given parts of Angular application, using Router Guards
  • how to intercept HTTP calls, adding an Access Token as it is required by the server
  • why we need a Refresh Token and how to use it transparently for the user
Table of contents
  • Application setup
  • Router Guards
  • JSON Web Token
  • Http interceptor
  • Refresh Token
  • AuthService
  • Summary
Application setup

Let’s think of the common use case where there are some pages (routes) in the application that the access to is restricted only for authorized users. After successful authentication, for example via a login form, the user is granted with an access to some restricted parts of the system (for example an admin page).

Authentication is the process of proving one’s identity. If we talk about login form, we assume that if a person is in the possession of the password associated with the given username, then that must be the person that this username belongs to.> Authorization happens after successful authentication and determines if the given user is authorized to access given resources (for example subpages in SPA).
For the sake of simplicity let’s assume that we have an application with a login page, available under /login route, and a page displaying a random number generated by the server, available under /secret-random-number. The random number page should be available only for the authorized users. If we manually try to access /secret-random-number we should be redirected back to the login page.

Router Guards

To achieve the goal of restricting access to /secret-random-number and redirecting back to the login page, in case the user is not logged in, we can make use of Angular’s built-in mechanism called Router Guards. These guards allow us to implement policies governing possible route transitions in an Angular application. Imagine a situation when a user tries to open a page that he has no access rights to. In such a case application should not allow this route transition. To achieve this goal we can make use of CanActivate guard. As Router Guards are just simple class providers, we need to implement a proper interface. Let’s take a look at below code snippet presenting AuthGuard.

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {

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

  canActivate() {
    if (this.authService.isLoggedIn()) {
      this.router.navigate(['/secret-random-number']);
    }
    return !this.authService.isLoggedIn();
  }
}

AuthGuard implements canActivate() which tells Angular router whether it can or cannot activate a particular route. To attach given guard to the route that it should protect, we just need to place its reference in canActivate property of that route as presented below. In our case, we want to protect the /login route. We want to allow users to open this route, only if they are not logged in. Otherwise, we redirect to /secret-random-number. The same approach applies to protecting other routes, with different policies implemented for given routes. Also, we can notice the canLoad property in below routes configuration. This kind of protection allows us to prevent a lazy-loaded route from being fetched from the server. Usually, canLoad guards implement the same policy as canActivate guards.

const routes: Routes = [
  { path: '', pathMatch: 'full', redirectTo: '/login' },
  {
    path: 'login',
    component: LoginComponent,
    canActivate: [AuthGuard]
  },
  {
    path: 'secret-random-number',
    loadChildren: './random/random.module#RandomModule',
    canActivate: [RandomGuard],
    canLoad: [RandomGuard]
  }
];

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

JSON Web Token

We came to the point where we have secured the routes in our application. The next step is to think about HTTP requests that the application sends to the server. If we only prevent the user from performing forbidden actions in our application, we will still be prone to the unauthorized HTTP calls that could be executed by the user, for example with any other HTTP client. Because of that, what is more important in securing a web application is making sure that the unauthorized server requests are not allowed. To make it possible for the server to recognize if the request is coming from an authorized user, we can attach an additional HTTP header indicating that fact. Here is the place where JSON Web Tokens (JWT) come into play.

The general idea standing behind JWT is to securely transmit information between parties. In our case, it is the user’s identity along with his rights, transmitted between the client (browser) and the server. When the user logs in, sending login query to the server, he receives back a JWT (aka access token) signed by the server with a private key. This private key should be known only to the server as it allows the server later to verify that the token is legitimate. When JWT is transmitted between the browser and the server, it is encoded with Base64 algorithm, that makes it look like a string of random characters (nothing could be further from the truth!). If you take a JWT and decode it with Base64 you will find a JSON object. Below you can find a decoded content of a JWT from our example application. On jwt.io you can play with JWT online.

Every JWT is composed of 3 blocks: header, payload, and signature. The header defines the type of the token and the used algorithm. The payload is the place where we put the data we want to securely transmit. In this case, we have a username, role, issuing timestamp (iat) and expiration timestamp (exp). The last block (HMACSHA256 function) is a signature generated with HMAC and SHA-256 algorithms. The signature guarantees not only that the token was created by a known party, but also the token’s integrity.

Integrity is the assurance of the accuracy and consistency of the data over its lifetime. In the case of JWT token, it means that it has not been altered during the transmission.

{
  "alg":"HS256",
  "typ":"JWT"
}
{
  "username": "user",
  "role": "admin",
  "iat": 1556172533,
  "exp": 1556173133
}
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  SECRET!
)

When the user successfully logs into the application and receives an access token, it has to be persisted somehow by the application. We can use for example local storage of the browser to save that token. It is fairly convenient and easy to implement, but it’s prone to XSS attacks. Another approach could be to use HttpOnly Cookie which is considered safer than local storage. Once we have JWT persisted, we will be attaching it to outgoing requests in HTTP Header. Before we dive into that aspect, let’s take a look at another important characteristic of JWT.

At this point, it is worth taking a closer look at the self-contained nature of JWT. When the server receives HTTP requests with JWT Access Token, it does not have to ask any persistence layer (for example database) for the verification of users rights. Those rights are inside the token. And since we guarantee authenticity and integrity of Access Token we can trust the information inside it. This is a really interesting feature of JWT because it opens the door for higher scalability of the system. Alternative scenarios would require saving some session id on the backend side and asking for it each and every time there is a need to authorize the request. Having self-contained Access Token, we don’t have to replicate token among server clusters or implement sticky sessions.

Http interceptor

Once we have our Access Token (JWT) persisted after user logs into the application, we want to use it to authorize outgoing requests. One approach could be to simply update every service that communicates with API to enrich requests with additional HTTP Header. This will result in a lot of duplicated code comparing to approach with HTTP Interceptor. The goal of HTTP Interceptor is to apply some processing logic to every outgoing request in the application.

Creating an HTTP interceptor is quite similar to creating a Router Guard. We need to have a class that implements a specific interface with the required method. In this case, it is HttpInterceptor with intercept method. Take a look at following code snippet with the interceptor from our example application. First, we want to check if the token is available with this.authService.getJwtToken(). If we have a token, we set an appropriate HTTP header. This code also contains error handling logic, which will be described later in this article.

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

  constructor(public authService: AuthService) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    if (this.authService.getJwtToken()) {
      request = this.addToken(request, this.authService.getJwtToken());
    }

    return next.handle(request).pipe(catchError(error => {
      if (error instanceof HttpErrorResponse && error.status === 401) {
        return this.handle401Error(request, next);
      } else {
        return throwError(error);
      }
    }));
  }

  private addToken(request: HttpRequest<any>, token: string) {
    return request.clone({
      setHeaders: {
        'Authorization': `Bearer ${token}`
      }
    });
  }
}

Having implemented our interceptor, it is necessary to register it as a provider with HTTP_INTERCEPTORS token in Angular module.

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

@NgModule({
  // declarations...
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: TokenInterceptor,
      multi: true
    }
  ]
  // imports...
})
export class AuthModule { }

Refresh Token

Taking into account that JWT is self-contained we need to think about one more thing - there is no way to invalidate it! If someone other than us gets into possession of the token there is very little we can do about it. That’s why it is a good idea to always give the token short time of validity. There are no strict rules of how long a token should live and it depends on the system requirements. A good starting point could be to have a token that is only valid for 15 minutes. After that time server would not consider this token valid and would not authorize requests with it.

So here comes another challenge - we don’t want to force the user to login into the application, let’s say, every 15 minutes. The solution to this problem is a Refresh Token. This kind of token lives somewhere on the server side (database, in-memory cache, etc) and is associated with the particular user’s session. It is important to notice that this token differs from JWT in many ways. First, it is not self-contained - it can be as simple as a unique random string. Second, we need to have it stored to be able to verify if user’s session is still alive. This gives us an ability to invalidate the session by simply removing the associated pair of [user, refresh_token]. When there is an incoming request with Access Token that has become invalid, the application can send a Refresh Token to obtain a new Access Token. If the user’s session is still alive, the server would respond with a new valid JWT. In our example, we will be sending Refresh Token transparently for the user, so that he is not aware of the refreshing process.

Let’s get back to our interceptor. If you remember from the previous code snippet, in case of HTTP 401 Error (Unauthorized) we have a special method handle401Error for handling this situation. Here comes a tricky part - we want to queue all HTTP requests in case of refreshing. This means that if the server responds with 401 Error, we want to start refreshing, block all requests that may happen during refreshing, and release them once refreshing is done. To be able to block and release requests during the refreshing, we will use BehaviorSubject as a semaphore.

First, we check if refreshing has not already started and set isRefreshing variable to true and populate null into refreshTokenSubject behavior subject. Later, the actual refreshing request starts. In case of success, isRefreshing is set to false and received JWT token is placed into the refreshTokenSubject. Finally, we call next.handle with the addToken method to tell interceptor that we are done with processing this request. In case the refreshing is already happening (the else part of the if statement), we want to wait until refreshTokenSubject contains value other than null. Using filter(token => token != null) will make this trick! Once there is some value other than null (we expect new JWT inside) we call take(1) to complete the stream. Finally, we can tell the interceptor to finish processing this request with next.handle.

private isRefreshing = false;
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);

private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
  if (!this.isRefreshing) {
    this.isRefreshing = true;
    this.refreshTokenSubject.next(null);

    return this.authService.refreshToken().pipe(
      switchMap((token: any) => {
        this.isRefreshing = false;
        this.refreshTokenSubject.next(token.jwt);
        return next.handle(this.addToken(request, token.jwt));
      }));

  } else {
    return this.refreshTokenSubject.pipe(
      filter(token => token != null),
      take(1),
      switchMap(jwt => {
        return next.handle(this.addToken(request, jwt));
      }));
  }
}

As you see, the combination of Access Token and Refresh Token is a tradeoff between scalability and security. Restricting the validity time of Access Token decreases the risk of an unwanted person using it, but using Refresh Token requires statefulness on the server.

AuthService

The last missing part of our solution is AuthService. This will be the place where we implement all the logic to handle logging in and out. Below you can find the source of that service and we will analyze it step by step.

Let’s start with the login method. Here we use HttpClient to execute post call to the server and apply some operators with pipe() method. By using tap() operator we are able to execute the desired side effect. On successful post method execution, we should receive Access Token and Refresh Token. The side effect we want to execute is to store these tokens calling doLoginUser. In this example, we make use of localstorage. Once stored, the value in the stream is mapped to true in order for the consumer of that stream to know that the operation succeeded. Finally, in case of error, we show the alert and return observable of false.

Side effect is a term used in Functional Programming. This concept is opposite to functional purity which means that there are no state changes in the system and the function always returns the result based on its inputs (regardless of the system state). If there is a state change (for example variable change) we call it side effect.
Implementation of the logout method is basically the same, apart from the fact, that inside of the request’s body we send refreshToken. This will be used by the server to identify who is attempting to log out. Then, the server will remove the pair of [user, refresh_token] and refreshing will not be possible anymore. Yet, Access Token will still be valid until it expires, but we remove it from the localstorage.

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

  private readonly JWT_TOKEN = 'JWT_TOKEN';
  private readonly REFRESH_TOKEN = 'REFRESH_TOKEN';
  private loggedUser: string;

  constructor(private http: HttpClient) {}

  login(user: { username: string, password: string }): Observable<boolean> {
    return this.http.post<any>(`${config.apiUrl}/login`, user)
      .pipe(
        tap(tokens => this.doLoginUser(user.username, tokens)),
        mapTo(true),
        catchError(error => {
          alert(error.error);
          return of(false);
        }));
  }

  logout() {
    return this.http.post<any>(`${config.apiUrl}/logout`, {
      'refreshToken': this.getRefreshToken()
    }).pipe(
      tap(() => this.doLogoutUser()),
      mapTo(true),
      catchError(error => {
        alert(error.error);
        return of(false);
      }));
  }

  isLoggedIn() {
    return !!this.getJwtToken();
  }

  refreshToken() {
    return this.http.post<any>(`${config.apiUrl}/refresh`, {
      'refreshToken': this.getRefreshToken()
    }).pipe(tap((tokens: Tokens) => {
      this.storeJwtToken(tokens.jwt);
    }));
  }

  getJwtToken() {
    return localStorage.getItem(this.JWT_TOKEN);
  }

  private doLoginUser(username: string, tokens: Tokens) {
    this.loggedUser = username;
    this.storeTokens(tokens);
  }

  private doLogoutUser() {
    this.loggedUser = null;
    this.removeTokens();
  }

  private getRefreshToken() {
    return localStorage.getItem(this.REFRESH_TOKEN);
  }

  private storeJwtToken(jwt: string) {
    localStorage.setItem(this.JWT_TOKEN, jwt);
  }

  private storeTokens(tokens: Tokens) {
    localStorage.setItem(this.JWT_TOKEN, tokens.jwt);
    localStorage.setItem(this.REFRESH_TOKEN, tokens.refreshToken);
  }

  private removeTokens() {
    localStorage.removeItem(this.JWT_TOKEN);
    localStorage.removeItem(this.REFRESH_TOKEN);
  }
}

Summary

We have covered the most important pieces of designing an authorization mechanism on the frontend side in Angular. You can find full sources of frontend and backend side under GitHub repositories:

Using JWT as an Access Token has a lot of benefits and it’s fairly simple to implement. However, you should be aware of the limitations and possible XSS Attacks. The way to minimize the risk is to use HttpOnly Cookies to store the tokens.

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.

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,

How to Add Authentication to Your Angular Application

How to Add Authentication to Your Angular Application

In this article, we discuss how to implement authentication, you’ll use Okta for authentication and access control. Be sure to take advantage of Okta’s Angular-specific libraries that make the entire process easier.

A Single-Page Application (SPA) is a web app or website that interacts with a user by rewriting current pages instead of loading entirely new pages from the server. While building a SPA, the developer, sooner or later, needs to think about authentication and authorization. Authentication usually requires a login page that can verify that a user is who he or she claims to be. After the user logs in, authorization works to grant or restrict specific resources.

Today, I’ll walk you through the steps to build an Angular application with authentication. You’ll use Okta for authentication and access control. Be sure to take advantage of Okta’s Angular-specific libraries that make the entire process easier.

Build an Angular SPA With Login

In this tutorial, I will focus purely on client-side security. I will not delve into the topic of server-side authentication or authorization. The application you will be implementing is a simple serverless online calculator. Access to the calculator will be restricted to users who have logged in. Naturally, real-life applications will communicate with the server and authenticate themselves with the server to gain access to restricted resources.

I will assume that you have installed Node on your system and that you are somewhat familiar with the node packet manager npm. The tutorial will use Angular 7. To install the Angular command line tool, open a terminal and enter the command:

npm install -g @angular/[email protected]

This will install the global ng command. On some systems, Node installs global commands in a directory that is not writable by regular users. In this case, you have to run the command above using sudo. Next, create a new Angular application. Navigate to a directory of your choice, and issue the following command.

ng new AngularCalculator

This starts a wizard that will walk you through creating a new application. The wizard will prompt you with two questions. When asked whether to add Angular routing in your application, answer yes. Next, you are given the choice of CSS technology. Choose plain CSS here, since the application you will be developing requires a small amount of styling. The next step is to install some libraries that will make it easier to create a pleasant, responsive design. Change into the AngularCalculator directory that you just created and run the command:

ng add @angular/material

This will prompt you with a few options. In the first question, you can choose the color theme. (I chose Deep Purple/Amber.) For the next two questions, answer yes to both using gesture recognition and browser animations. On top of Material Design, I will be using Flex Layout components. Run the command:

npm install @angular/[email protected]

Next, add some general styling to the application. Open src/style.css and replace the contents with the following.

body {
  margin: 0;
  font-family: sans-serif;
}

h1, h2 {
  text-align: center;
}

The next step is to make the Material Design components available inside the Angular application. Replace the contents of the file src/app/app.module.ts with the following code.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FlexLayoutModule } from "@angular/flex-layout";

import { MatToolbarModule,
         MatMenuModule,
         MatIconModule,
         MatCardModule,
         MatButtonModule,
         MatTableModule,
         MatDividerModule } from '@angular/material';

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

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    BrowserAnimationsModule,
    FlexLayoutModule,
    MatToolbarModule,
    MatMenuModule,
    MatIconModule,
    MatCardModule,
    MatButtonModule,
    MatTableModule,
    MatDividerModule,
    AppRoutingModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

The file src/app/app.component.html contains the template for the main application component. This component acts as a container for the application and all its components. I like to create a basic toolbar layout in this component. Open the file and replace the content with the following.

<mat-toolbar color="primary" class="expanded-toolbar">
  <span>
    <button mat-button routerLink="/">{{title}}</button>
    <button mat-button routerLink="/"><mat-icon>home</mat-icon></button>
  </span>
  <button mat-button routerLink="/calculator"><mat-icon>dialpad</mat-icon></button>
</mat-toolbar>
<router-outlet></router-outlet>

To add some styling, open src/app/app.component.css and add the following lines.

.expanded-toolbar {
  justify-content: space-between;
  align-items: center;
}

The <router-outlet> tag in the HTML template acts as a placeholder for the components that are managed by the router. Create these components next. The application will be made up of two views. The home view shows a simple splash screen containing information about the application. The calculator component contains the actual calculator. To create the components for these views, open the terminal again in the application’s main directory and run the ‘ng generate’ command for each.

ng generate component home
ng generate component calculator

This will create two directories under src/app, one for each component. You will only add two simple headers to the splash screen. Open src/app/home/home.component.html and replace the content with the following.

<h1>Angular Calculator</h1>
<h2>A simple calculator with login</h2>

The calculator component contains the main meat of the application. Start by creating the layout template for the calculator’s buttons and display in src/app/calculator/calculator.component.html.

<h1 class="h1">Calculator</h1>
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" class="products">
  <mat-card class="mat-elevation-z1 calculator">
    <p class="display">{{display}}</p>
    <div>
      <button mat-raised-button color="warn" (click)="acPressed()">AC</button>
      <button mat-raised-button color="warn" (click)="cePressed()">CE</button>
      <button mat-raised-button color="primary" (click)="percentPressed()">%</button>
      <button mat-raised-button color="primary" (click)="operatorPressed('/')">÷</button>
    </div>
    <div>
      <button mat-raised-button (click)="numberPressed('7')">7</button>
      <button mat-raised-button (click)="numberPressed('8')">8</button>
      <button mat-raised-button (click)="numberPressed('9')">9</button>
      <button mat-raised-button color="primary" (click)="operatorPressed('*')">x</button>
    </div>
    <div>
      <button mat-raised-button (click)="numberPressed('4')">4</button>
      <button mat-raised-button (click)="numberPressed('5')">5</button>
      <button mat-raised-button (click)="numberPressed('6')">6</button>
      <button mat-raised-button color="primary" (click)="operatorPressed('-')">-</button>
    </div>
    <div>
      <button mat-raised-button (click)="numberPressed('1')">1</button>
      <button mat-raised-button (click)="numberPressed('2')">2</button>
      <button mat-raised-button (click)="numberPressed('3')">3</button>
      <button mat-raised-button color="primary" class="tall" (click)="operatorPressed('+')">+</button>
    </div>
    <div>
      <button mat-raised-button (click)="numberPressed('0')">0</button>
      <button mat-raised-button (click)="numberPressed('.')">.</button>
      <button mat-raised-button color="primary" (click)="equalPressed()">=</button>
    </div>
  </mat-card>
</div>

You will notice the (click) property on the buttons. This property allows you to specify the member functions of the component’s class that will be called when the button is clicked. Before you get around to implementing that class, add a little bit of styling in src/app/calculator/calculator.component.css.

.display {
  background-color: #f8f8f8;
  line-height: 24px;
  padding: 5px 8px;
}

.calculator button {
  margin: 5px;
  width: 64px;
}

.calculator button.tall {
  float: right;
  height: 82px;
}

The component’s class lives in the file src/app/calculator/calculator.component.ts. Open this file and replace its contents with the following code.

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

@Component({
  selector: 'app-calculator',
  templateUrl: './calculator.component.html',
  styleUrls: ['./calculator.component.css']
})
export class CalculatorComponent implements OnInit {
  private stack: (number|string)[];
  display: string;

  constructor() { }

  ngOnInit() {
    this.display = '0';
    this.stack = ['='];
  }

  numberPressed(val: string) {
    if (typeof this.stack[this.stack.length - 1] !== 'number') {
      this.display = val;
      this.stack.push(parseInt(this.display, 10));
    } else {
      this.display += val;
      this.stack[this.stack.length - 1] = parseInt(this.display, 10);
    }
  }

  operatorPressed(val: string) {
    const precedenceMap = {'+': 0, '-': 0, '*': 1, '/': 1};
    this.ensureNumber();
    const precedence = precedenceMap[val];
    let reduce = true;
    while (reduce) {
      let i = this.stack.length - 1;
      let lastPrecedence = 100;

      while (i >= 0) {
        if (typeof this.stack[i] === 'string') {
          lastPrecedence = precedenceMap[this.stack[i]];
          break;
        }
        i--;
      }
      if (precedence <= lastPrecedence) {
        reduce = this.reduceLast();
      } else {
        reduce = false;
      }
    }

    this.stack.push(val);
  }

  equalPressed() {
    this.ensureNumber();
    while (this.reduceLast()) {}
    this.stack.pop();
  }

  percentPressed() {
    this.ensureNumber();
    while (this.reduceLast()) {}
    const result = this.stack.pop() as number / 100;
    this.display = result.toString(10);
  }

  acPressed() {
    this.stack = ['='];
    this.display = '0';
  }

  cePressed() {
    if (typeof this.stack[this.stack.length - 1] === 'number') { this.stack.pop(); }
    this.display = '0';
  }

  private ensureNumber() {
    if (typeof this.stack[this.stack.length - 1] === 'string') {
      this.stack.push(parseInt(this.display, 10));
    }
  }

  private reduceLast() {
    if (this.stack.length < 4) { return false; }
    const num2 = this.stack.pop() as number;
    const op = this.stack.pop() as string;
    const num1 = this.stack.pop() as number;
    let result = num1;
    switch (op) {
      case '+': result = num1 + num2;
        break;
      case '-': result = num1 - num2;
        break;
      case '*': result = num1 * num2;
        break;
      case '/': result = num1 / num2;
        break;
    }
    this.stack.push(result);
    this.display = result.toString(10);
    return true;
  }
}

This code contains a complete calculator. See how the callback function for the buttons in the HTML template has been implemented as member functions. The calculator knows the four basic operations +-*, and /, and it is aware of operator precedence. I will not go into the details of how this is being achieved. I’ll leave this for you to figure out as an exercise.

Before you can start testing the application, you need to register the new components with the router. Open up src/app/app-routing.module.ts and edit its contents to match the following.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { HomeComponent } from './home/home.component';
import { CalculatorComponent } from './calculator/calculator.component';

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'calculator', component: CalculatorComponent },
];

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

The demo application is now complete and you can fire up the server. The Angular command line tool comes with a development server that is ideal for testing the application. It will automatically cause the browser to reload the application whenever you make any changes to the code. To start the development server, simply run the following command.

ng serve

Open up your browser, navigate to http://localhost:4200, and click on the calculator icon in the top right corner. You should see something like the image below.

Add Authentication to Your Angular App

In this section, I will show you how to add authentication to your application. Okta provides a simple solution to secure authentication with easy integration into Angular applications. The ready-made route guard lets you restrict access to selected routes simply by dropping it into the route specification. The application flow is as follows. Whenever a user requests a protected resource, the route guard will check if the user is logged in. If the user is not logged in the guard will redirect the user to a hosted login page on the Okta servers. Alternatively, the user may opt to click on a login link directly. In this case, the authentication service will redirect the user to the login page. Once the user is logged in, the login page will redirect the user back to a special route, usually called implicit/callback, in the application. This route is managed by the Okta Callback Component. The callback component will decide where to redirect the user, depending on the original request and on the authentication status of the user.

Before you start, you will need to register a free developer account with Okta. Open your browser, navigate to developer.okta.com, and click the Create Free Account button. In the form that appears next, enter your details. Complete your registration by clicking the Get Started button.

Once you have gone through the registration process you will be taken to your Okta dashboard. Select the Applications link in the top menu. Here you can see an overview of all the applications that you have linked to your Okta account. If you are a new member, this list will be empty. To create your first application, click on the green button that says Add Application. On the screen that appears next, select Single-Page App and click Next. On the next screen, you can edit the settings. The Base URI should point to the location of your application. Since you are using the standard settings for the Angular development server, this should be set to http://localhost:4200/. The Login Redirect URI is the location that the user will be redirected back to after a successful login. This should match the callback route in your application, so you need to set it to http://localhost:4200/implicit/callback. When you’re done, click on the Done button. The resulting screen will provide you with a client ID which you need to copy and paste into your application.

_Angular app on Okta_

To start implementing authentication in your application, you need to install the Okta Angular library. Open the terminal in the application directory and run the command:

npm install @okta/[email protected]

Open src/app/app.module.ts and add the following import to the top of the file.

import { OktaAuthModule } from '@okta/okta-angular';

In the same file, in the imports section of the @NgModule annotation, add the following code.

OktaAuthModule.initAuth({
  issuer: 'https://okta.okta.com/oauth2/default',
  redirectUri: 'http://localhost:4200/implicit/callback',
  clientId: '{yourClientId}'
})

The {yourClientId} placeholder should be replaced with the client ID that you obtained in the Okta dashboard. Open src/app/app.component.ts and replace the contents of the file with the following.

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  title = 'AngularCalculator';
  isAuthenticated: boolean;

  constructor(public oktaAuth: OktaAuthService) {
    this.oktaAuth.$authenticationState.subscribe(
      (isAuthenticated: boolean)  => this.isAuthenticated = isAuthenticated
    );
  }

  ngOnInit() {
    this.oktaAuth.isAuthenticated().then((auth) => {
      this.isAuthenticated = auth;
    });
  }

  login() {
    this.oktaAuth.loginRedirect();
  }

  logout() {
    this.oktaAuth.logout('/');
  }
}

The OktaAuthService is injected into the main application component. The main component contains a isAuthenticated flag that keeps track of the authentication status of the user. By subscribing to the $authenticationState observable, this flag is kept up to date whenever the status changes. The flag is initialized in the ngOnInit function. The login member function simply calls OktaAuthService.loginRedirect, which redirects the user to the hosted login page. Similarly, the logout member function calls OktaAuthService.logout, which erases any user tokens and redirects the user to the main route.

Next, open src/app/app.component.html and add the following code into the <mat-toolbar> after the closing tag of the first <span> element.

<span>
  <button mat-button *ngIf="!isAuthenticated" (click)="login()"> Login </button>
  <button mat-button *ngIf="isAuthenticated" (click)="logout()"> Logout </button>
</span>

By making use of the isAuthenticated flag, either a Login or a Logout button is shown. Each button calls the respective method of the application component to log the user in or out. In the final step, you need to modify the router settings. Open src/app/app-routing.module.ts and add the following import to the top of the file.

import { OktaCallbackComponent, OktaAuthGuard } from '@okta/okta-angular';

In the code above and in the Okta dashboard settings, you have specified that implicit/callback route should handle the login callback. To register the OktaCallbackComponent with this route, add the following entry to the routes setting.

{ path: 'implicit/callback', component: OktaCallbackComponent }

The OktaAuthGuard can be used to restrict access to any protected routes. To protect the calculator route, modify its entry by adding a canActivate property in the following way.

{ path: 'calculator', component: CalculatorComponent, canActivate: [OktaAuthGuard] }

Now, if you try to access the calculator in the application, you will be redirected to the Okta login page. Only on a successful login are you going to be redirected back to the calculator. The splash screen, on the other hand, is accessible without any authentication.

Angular Authentication with the Login Widget

Redirecting the user to an external login page is OK for some use cases. In other cases, you don’t want the user to leave your site. This is a use case for the login widget. It lets you embed the login form directly into your application. To make use of the widget, you first have to install it. Open the terminal in the application directory and install the following packages.

npm install @okta/[email protected] [email protected]

Next, generate a component that hosts the login form. This component will not need any additional styling, and the HTML template will consist only of a single tag that can be inlined in the component definition.

ng generate component login --inline-style=true --inline-template=true

Open the newly created src/app/login/login.component.ts and paste the following contents into it.

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

import { OktaAuthService } from '@okta/okta-angular';
import * as OktaSignIn from '@okta/okta-signin-widget';

@Component({
  selector: 'app-login',
  template: `<div id="okta-signin-container"></div>`,
  styles: []
})
export class LoginComponent implements OnInit {
  widget = new OktaSignIn({
    baseUrl: 'https://okta.okta.com'
  });

  constructor(private oktaAuth: OktaAuthService, router: Router) {
    // Show the widget when prompted, otherwise remove it from the DOM.
    router.events.forEach(event => {
      if (event instanceof NavigationStart) {
        switch(event.url) {
          case '/login':
          case '/calculator':
            break;
          default:
            this.widget.remove();
            break;
        }
      }
    });
  }

  ngOnInit() {
    this.widget.renderEl({
      el: '#okta-signin-container'},
      (res) => {
        if (res.status === 'SUCCESS') {
          this.oktaAuth.loginRedirect('/', { sessionToken: res.session.token });
          // Hide the widget
          this.widget.hide();
        }
      },
      (err) => {
        throw err;
      }
    );
  }
}

In src/index.html, add the following two lines inside the <head> tag to include the default widget styles.

<link href="https://ok1static.oktacdn.com/assets/js/sdk/okta-signin-widget/2.14.0/css/okta-sign-in.min.css" type="text/css" rel="stylesheet"/>
<link href="https://ok1static.oktacdn.com/assets/js/sdk/okta-signin-widget/2.14.0/css/okta-theme.css" type="text/css" rel="stylesheet"/>

Now, add the login route to the route configuration. Open src/app/app-routing.module.ts. Add an import for the LoginComponent at the top of the file.

import { LoginComponent } from './login/login.component';

Next, add a function that tells the router what to do when the user is required to log in.

export function onAuthRequired({ oktaAuth, router }) {
  router.navigate(['/login']);
}

Make sure that the function is exported. In the routes specification, add the route for the login component.

{ path: 'login', component: LoginComponent }

Finally, modify the specification for the calculator route to include a reference to the onAuthRequired function.

{
  path: 'calculator',
  component: CalculatorComponent,
  canActivate: [OktaAuthGuard],
  data: { onAuthRequired }
}

The next step is to make sure that the user is directed to the login page when the login button in the top bar is pressed. Open src/app/app.component.html and change the line containing the login button to the following.

<button mat-button *ngIf="!isAuthenticated" routerLink="/login"> Login </button>

You can also remove the login function in src/app/app.component.ts, as it is no longer needed.

That’s it! Your application now hosts its own login form powered by Okta. Below is a screenshot of what the login widget might look like.

_Angular calculator with sign-in widget_ Conclusion

In this tutorial, I showed you how to implement authentication and basic authorization in a single page application based on Angular. You have the choice between a hosted login page and a login widget embedded in your application. The hosted login is ideal when you know that there are multiple applications linked to a single user account. In this case, the hosted solution conveys the idea that the user is logging on to all applications in one central location. The login widget is the ideal solution when you want to provide a seamless experience in a single branded application.

The code for this tutorial can be found on GitHub

You may also like: Add Authentication to Your Angular PWA

Did you like this tutorial? Please share if you liked it!