Angular Spring Boot Security Basic Authentication Example

In the previous tutorial, we implemented an Angular 8 + Spring boot hello world example. In this tutorial, we will be implementing Basic login authentication using Spring Boot and Spring Security to secure the REST service created in the previous tutorial. We will implement basic login and logout features. All the REST calls made from Angular to Spring Boot will be authenticated using Basic Authentication.
Spring boot made the easiest way to secure REST services by adding a very simple dependency - spring boot starter security:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
In the previous tutorial, we created a simple spring boot hello world application and we will continue with this application and implement login, and logout features.
Let's first implement security in the Spring boot hello world application.
Full code on GitHub at https://github.com/RameshMF/angular8-springboot-basic-auth-login-logout

Source Code on GitHub

Get the source code of this tutorial on my GitHub Repository.

Server Side - Security implementation in Spring boot application

Add spring-boot-starter-security Dependency

Let's add the spring-boot-starter-security dependency to our pom.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<project
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>net.javaguides.springboot</groupId>
    <artifactId>Springboot-helloworld-application</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>Springboot-helloworld-application</name>
    <description>Demo project for Spring Boot</description>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
        <relativePath/>
        <!-- lookup parent from repository -->
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
Once Spring Security is on the classpath, then Spring Boot automatically secures all HTTP endpoints with "basic" authentication.

Default Security Setup

In order to add security to our Spring Boot application, we need to add the security starter dependency:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
This will include the SecurityAutoConfiguration class – containing the initial/default security configuration.
Simply put, by default, the Authentication gets enabled for the Application. Also, content negotiation is used to determine if basic or formLogin should be used.
When you start the spring boot project, the default password is randomly generated and printed in the console log:
 c8be15de-4488-4490-9dc6-fab3f91435c6
Default username - user
Default password - c8be15de-4488-4490-9dc6-fab3f91435c6

Configure standard user and password

We can override the default user and password using the below properties in the application.properties file:
spring.security.user.name=javaguides
spring.security.user.password=password

Configure WebSecurityConfigurerAdapter

To enable authentication and authorization support in spring boot rest APIs, we can configure a utility class WebSecurityConfigurerAdapter. It helps in requiring the user to be authenticated before accessing any configured URL (or all URLs) within our application.
package net.javaguides.springboot.helloworldapp.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().
        disable()
            .authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS, "/**")
            .permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .httpBasic();
    }
}

Define AuthenticationBean and BasicAuthController

Let's create an AuthenticationBean, which is used to return a success message to the client:
package net.javaguides.springboot.helloworldapp.bean;

public class AuthenticationBean {

    private String message;

    public AuthenticationBean(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return String.format("HelloWorldBean [message=%s]", message);
    }
}
Let's create a BasicAuthController class with /basicauth REST API for returning the authentication success message.
package net.javaguides.springboot.helloworldapp.controller;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import net.javaguides.springboot.helloworldapp.bean.AuthenticationBean;

@CrossOrigin(origins = "http://localhost:4200")
@RestController
@RequestMapping("/api/v1")
public class BasicAuthController {

    @GetMapping(path = "/basicauth")
    public AuthenticationBean basicauth() {
        return new AuthenticationBean("You are authenticated");
    }
}

Testing the above Security Implementation using Postman Rest Client

Frontend Side - Basic Login and Logout implementation in Angular 8 Application

We will be modifying the code we developed in the previous tutorial. The angular project we will be developing is as follows - 

Login Authentication Implementation

Let's create a login component with the following command:
C:\angular-frontend\src\app> ng g c login
CREATE src/app/login/login.component.html (20 bytes)
CREATE src/app/login/login.component.spec.ts (621 bytes)
CREATE src/app/login/login.component.ts (265 bytes)
CREATE src/app/login/login.component.css (0 bytes)
UPDATE src/app/app.module.ts (724 bytes)
Let's create an Angular auth service with the following command:
PS C:\angular-frontend\src\app> ng g s /login/auth
CREATE src/app/login/auth.service.spec.ts (323 bytes)
CREATE src/app/login/auth.service.ts (133 bytes)
Let's understand each file generated above and implement the login feature.

LoginComponent

Open the LoginComponent file and add the following code to it -
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { AuthenticationService } from './auth.service';

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

  username: string;
  password : string;
  errorMessage = 'Invalid Credentials';
  successMessage: string;
  invalidLogin = false;
  loginSuccess = false;

  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private authenticationService: AuthenticationService) {   }

  ngOnInit() {
  }

  handleLogin() {
    this.authenticationService.authenticationService(this.username, this.password).subscribe((result)=> {
      this.invalidLogin = false;
      this.loginSuccess = true;
      this.successMessage = 'Login Successful.';
      this.router.navigate(['/hello-world']);
    }, () => {
      this.invalidLogin = true;
      this.loginSuccess = false;
    });      
  }
}

Login HTML Template

Let's open login.component.html file and add the following code to it -
<app-menu></app-menu>
<div class="container col-lg-6">
    <h1 class="text-center">Login</h1>
    <div class="card">
      <div class="card-body">
        <form class="form-group">
          <div class="alert alert-warning" *ngIf='invalidLogin'>{{errorMessage}}</div>
          <div class="alert alert-success" *ngIf='loginSuccess'>{{successMessage}}</div>
          <div class="form-group">
            <label for="email">User Name :</label>
            <input type="text" class="form-control" id="username" [(ngModel)]="username" placeholder="Enter User Name"
              name="username">
          </div>
          <div class="form-group">
            <label for="pwd">Password:</label>
            <input type="password" class="form-control" [(ngModel)]="password" id="password" placeholder="Enter password"
              name="password">
          </div>
          <button (click)=handleLogin() class="btn btn-success">Login</button>
        </form>
      </div>
    </div>
  </div>

Add the login path to the routing module:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HelloWorldComponent } from './hello-world/hello-world.component';
import { LoginComponent } from './login/login.component';

const routes: Routes = [
  {path: 'login', component: LoginComponent},
  {path: '', component: LoginComponent},
  {path: 'hello-world', component: HelloWorldComponent},
  {path: 'logout', component: LoginComponent},
];

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

export class AppRoutingModule { }

AuthenticationService

Let's create AuthenticationService class which makes a basic auth Rest API call for basic authentication. We also need to store authenticated user in sessionStorage for logout implementation. 
Let's open auth.service.ts file and add the following content to it -
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { map } from 'rxjs/operators';

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

  // BASE_PATH: 'http://localhost:8080'
  USER_NAME_SESSION_ATTRIBUTE_NAME = 'authenticatedUser'

  public username: String;
  public password: String;

  constructor(private http: HttpClient) {

  }

  authenticationService(username: String, password: String) {
    return this.http.get(`http://localhost:8080/api/v1/basicauth`,
      { headers: { authorization: this.createBasicAuthToken(username, password) } }).pipe(map((res) => {
        this.username = username;
        this.password = password;
        this.registerSuccessfulLogin(username, password);
      }));
  }

  createBasicAuthToken(username: String, password: String) {
    return 'Basic ' + window.btoa(username + ":" + password)
  }

  registerSuccessfulLogin(username, password) {
    sessionStorage.setItem(this.USER_NAME_SESSION_ATTRIBUTE_NAME, username)
  }

  logout() {
    sessionStorage.removeItem(this.USER_NAME_SESSION_ATTRIBUTE_NAME);
    this.username = null;
    this.password = null;
  }

  isUserLoggedIn() {
    let user = sessionStorage.getItem(this.USER_NAME_SESSION_ATTRIBUTE_NAME)
    if (user === null) return false
    return true
  }

  getLoggedInUserName() {
    let user = sessionStorage.getItem(this.USER_NAME_SESSION_ATTRIBUTE_NAME)
    if (user === null) return ''
    return user
  }
}

Add and Configure HttpInterceptor

We will be creating a new HttpInterceptor service called BasicAuthInterceptor Service. This service will check if the session has a valid username and basicauth String, then it will update the headers of all outgoing HTTP requests. We implement the interceptor by extending the HttpInterceptor.
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { AuthenticationService } from './login/auth.service';

@Injectable()
export class HttpInterceptorService implements HttpInterceptor {

    constructor(private authenticationService: AuthenticationService) { }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (this.authenticationService.isUserLoggedIn() && req.url.indexOf('basicauth') === -1) {
            const authReq = req.clone({
                headers: new HttpHeaders({
                    'Content-Type': 'application/json',
                    'Authorization': `Basic ${window.btoa(this.authenticationService.username + ":" + this.authenticationService.password)}`
                })
            });
            return next.handle(authReq);
        } else {
            return next.handle(req);
        }
    }
}
Now we will register the created HTTPInterceptor using the app.module.ts by updating it in the provider section.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HelloWorldComponent } from './hello-world/hello-world.component';
import { MenuComponent } from './menu/menu.component';
import { LoginComponent } from './login/login.component';

import { FormsModule } from '@angular/forms';
import { LogoutComponent } from './logout/logout.component';
import { HttpInterceptorService } from './httpInterceptor.service';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent,
    HelloWorldComponent,
    MenuComponent,
    LoginComponent,
    LogoutComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    FormsModule
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: HttpInterceptorService,
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Logout Implementation

Let's create a logout component with the following command:
C:\angular-frontend\src\app> ng g c logout
CREATE src/app/logout/logout.component.html (21 bytes)
CREATE src/app/logout/logout.component.spec.ts (628 bytes)
CREATE src/app/logout/logout.component.ts (269 bytes)
CREATE src/app/logout/logout.component.css (0 bytes)

Add Logout Button to Menu Template

In the previous tutorial, we created a Menu Html template, and let's update it with the logout button.
<header>
    <nav class="navbar navbar-expand-md navbar-dark bg-dark">
        <div><a href="https://www.javaguides.net" class="navbar-brand">JavaGuides</a></div>
        <ul class="navbar-nav navbar-collapse justify-content-end">
            <li>
                <a *ngIf="isLoggedIn" class="nav-link" href="/logout" (click)=handleLogout()>Logout</a>
            </li>
        </ul>
    </nav>
  </header>

Update MenuComponent

In the previous tutorial, we created MenuComponent, and let's update it for logout implementation. Once we will click on the logout button then the user stored in sessionStorage will be removed. 
Let's open the menu.component.ts file and add the below code to it -
import { Component, OnInit } from '@angular/core';
import { AuthenticationService } from '../login/auth.service';
import { ActivatedRoute, Router } from '@angular/router';

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

  isLoggedIn = false;

  constructor(private route: ActivatedRoute,
    private router: Router,
    private authenticationService: AuthenticationService) { }

  ngOnInit() {
    this.isLoggedIn = this.authenticationService.isUserLoggedIn();
    console.log('menu ->' + this.isLoggedIn);
  }

  handleLogout() {
    this.authenticationService.logout();
  }
}

Update Routing Module

After the user logout, a user should navigate to the login screen so let's update the AppRoutingModule file as -
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HelloWorldComponent } from './hello-world/hello-world.component';
import { LoginComponent } from './login/login.component';

const routes: Routes = [
  {path: 'login', component: LoginComponent},
  {path: '', component: LoginComponent},
  {path: 'hello-world', component: HelloWorldComponent},
**  {path: 'logout', component: LoginComponent},**
];

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

export class AppRoutingModule { }

Demo

The below GIF image shows the demo of this application.
Username: javaguides
Password: password
Get the source code of this tutorial on my GitHub Repository.

Comments

  1. The 'spring-boot-starter-test' artifact has 1000's of dependencies like couchbase, cassandra, elastic search, mongodb etc ... which are not at all required in app ...

    Can you remove those and just put the required dependencies instead ?

    ReplyDelete
  2. Hello! After refresh page the logged in user is remove. How can i save authorization data and get it after refresh with out login again?

    ReplyDelete
    Replies
    1. Hi, Did you find the solution? Same problem here. The problem is from back end.

      Delete
    2. same problem

      Delete
    3. its not the backend, its the front end.

      You are only saving the username and password as properties in the auth service not in the session.

      You want to be saving the token in the session and then using the token in the interceptor instead of calling the username and password from the service.

      1. in the authenicationService method:
      let basicAuthToken = this.createBasicAuthToken(username, password)

      2. Save it to the session in registerSuccessfulLogin(basicAuthToken):
      sessionStorage.setItem(this.SESSION_TOKEN, basicAuthToken);

      3. Use the token from the session in the interceptor:
      'Authorization': sessionStorage.getItem(this.SESSION_TOKEN)

      Delete
  3. I am trying to make a call to the database and validate the user instead of properties file.
    How can I, and I want to know how the username and password is reading from properties file they didn't called application.properties file anywhere

    ReplyDelete
    Replies
    1. Spring boot internally process this application.properties file so we no need to manually call and process it. You can this tutorial to validate username and password with database at https://www.javaguides.net/2019/10/spring-security-hibernate-database-authentication-example.html

      Delete
    2. in the current example is there any chance to stop the spring security.. just I want to authenticate from my db call

      Delete
  4. Thank you for your tutorial.I followed above task and when i was trying to login i had following error.
    "Access to XMLHttpRequest at 'http://localhost:8080/api/v1/basicauth' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource".
    Can you please tell me any solution for that?

    ReplyDelete
    Replies
    1. Did you find a solution? Having the same issue

      Delete
    2. Add cors() to the configure method in SpringSecurityConfig. For example:

      @Override
      protected void configure(HttpSecurity http) throws Exception {
      http.csrf().
      disable()
      .authorizeRequests()
      .antMatchers(HttpMethod.OPTIONS, "/**")
      .permitAll()
      .anyRequest()
      .authenticated()
      .and()
      .httpBasic()
      .and()
      .cors();
      }

      Delete
    3. Goto the c drive where you have kept the chrome and run below command inside the application folder example : C:\Program Files (x86)\Google\Chrome\Application
      run below command:-
      chrome.exe --user-data-dir="C:/Chrome dev session" --disable-web-security
      this should solve the problem

      Delete

Post a Comment

Leave Comment