{{announcement.body}}
{{announcement.title}}

Develop a Secure CRUD Application Using Angular and Spring Boot

DZone 's Guide to

Develop a Secure CRUD Application Using Angular and Spring Boot

In this article, we discuss how to implement OpenID Connect with Angular, Kotlin, Spring Boot, and Okta.

· Java Zone ·
Free Resource

Angular has been around for quite some time. Angular JS, the original version, was one of the first JavaScript MVC frameworks to dominate the web landscape and gained a strong developer fan base in the early 2010s. It’s still going strong if the recent release of Angular 9 is any indication!

Angular 9 pairs perfectly with Spring Boot, one of the most popular frameworks for developing Java apps and REST APIs. Spring Boot revolutionized the way Java developers write Spring apps by introducing pre-configured starters with smart defaults. 

Read on to learn how you can use this dynamic duo to build a CRUD app.

Spring Boot and Angular

What’s New In Angular 9?

Angular 9’s most prominent new feature is Ivy. Ivy is Angular’s new compiler and renderer. The renderer is the engine that takes your components and templates and translates them into instructions to manipulate the DOM. Ivy is an internal component, so you don’t interact with it directly. However, it can have a significant impact on your code, yielding much smaller JavaScript bundles and increasing performance.

Simply put: upgrading to Angular 9 will make your web apps faster!

Other notable features in Angular 9 include:

  1. Enhancements to Angular CLI to support internationalization (i18n).
  2. Type-safe improvements to TestBed for unit testing.

To use Angular CLI’s i18n feature, you simply need to add the i18n attribute to your text elements, then run ng xi18n to extract your localizable text into a file.

TestBed.inject(Component) is the new way to get references to components in tests. TestBed.get(Component) is deprecated.

What’s New in Spring Boot 2.2?

Spring Boot 2.2 was released in September 2019 and focuses on performance improvements and reduced memory usage. It adds Java 13 support, RSocket support, and the ability to group health indicators. Grouping indicators can be useful if you’re deploying to Kubernetes and want different groups for "liveness" and "readiness" probes.

In this post, I’ll show you how to build a CRUD application with Angular 9 and Spring Boot 2.2. Along the way, I’ll do my best to weave in security tips and how to make your apps more secure.

Prerequisites:

To install Node and Java on a Mac, Linux, or Windows Subsystem for Linux (WSL), you can use Homebrew.

Shell
 




x


 
1
brew install node
2
brew tap AdoptOpenJDK/openjdk
3
brew cask install adoptopenjdk11



You can also use SDKMAN! to install Java 11.

Shell
 




xxxxxxxxxx
1


1
sdk install java 11.0.5.hs-adpt



You can refer to the table of contents below for the steps in this tutorial.

Create an Angular 9 App

To create an Angular 9 app, you first need to install Angular CLI.

Shell
 




xxxxxxxxxx
1


1
npm install -g @angular/cli@9.0.2



Then, create a directory on angular-spring-boot. Open a terminal window and navigate to this directory. Run ng new to create an Angular application.

Shell
 




xxxxxxxxxx
1


1
ng new notes --routing --style css



This process will take a minute or two to complete, depending on your internet speed and hardware. Once it’s finished, navigate into the directory and run ng serve.

Shell
 




xxxxxxxxxx
1


 
1
ng serve



Open your browser to http://localhost:4200, and you’ll see the default homepage.

Angular default homepage

Stop the ng serve process using Ctrl+C in your terminal.

You may also like: Spring Boot Security + JWT ''Hello World'' Example.

Add Angular Authentication Using OpenID Connect

OpenID Connect (OIDC) is an identity later based on the OAuth 2.0 specification. It leverages JSON Web Tokens (JWT) to provide an ID token and other features like discoverability and a /userinfo endpoint.

Okta has Authentication and User Management APIs that reduce development time with instant-on, scalable user infrastructure. Okta’s intuitive API and expert support make it easy for developers to authenticate, manage, and secure users + roles in any application.

To add OIDC login support to your Angular 9 app, you’ll first need a free Okta developer account. After you’ve created your account and logged in to your Okta dashboard, create a new OIDC app using the following steps:

1. Navigate to Applications > Add Application.

2. Select Single-Page App and click Next.

3. Provide a name (e.g., Angular 9) and change the Login redirect URI to http://localhost:4200/implicit/callback

4. Click Done.

Your app’s settings should resemble the following.

Application settings

Application settings

At the bottom of the screen, you’ll see your app’s Client ID. You might also notice that Use PKCE is selected. This setting provides the maximum level of security you can currently have for single-page apps when using OIDC for auth.

Note: To learn more about PKCE (pronounced "pixy"), see Implement the OAuth 2.0 Authorization Code with PKCE Flow.

Copy your client ID and your issuer URI (from API > Authorization Servers) into the following command.

Shell
 




xxxxxxxxxx
1


1
ng add @oktadev/schematics --issuer=$issuer --clientId=$clientId



This command adds Okta’s Angular SDK and configures OIDC authentication for your app.

Okta Angular SDK

Okta Angular SDK

It creates a home.component.ts that has authentication logic as well as a template that renders login and logout buttons.

src/app/home/home.component.ts

TypeScript
 




xxxxxxxxxx
1
22


 
1
import { Component, OnInit } from '@angular/core';
2
import { OktaAuthService } from '@okta/okta-angular';
3
 
          
4
@Component({
5
  selector: 'app-home',
6
  templateUrl: './home.component.html',
7
  styleUrls: ['./home.component.css']
8
})
9
export class HomeComponent implements OnInit {
10
  isAuthenticated: boolean;
11
 
          
12
  constructor(public oktaAuth: OktaAuthService) {
13
  }
14
 
          
15
  async ngOnInit() {
16
    this.isAuthenticated = await this.oktaAuth.isAuthenticated();
17
    // Subscribe to authentication state changes
18
    this.oktaAuth.$authenticationState.subscribe(
19
      (isAuthenticated: boolean)  => this.isAuthenticated = isAuthenticated
20
    );
21
  }
22
}



src/app/home/home.component.html

HTML
 




xxxxxxxxxx
1


 
1
<div>
2
  <button *ngIf="!isAuthenticated" (click)="oktaAuth.loginRedirect()">Login</button>
3
  <button *ngIf="isAuthenticated" (click)="oktaAuth.logout()">Logout</button>
4
</div>



There’s also an HttpInterceptor created to add an access token to outgoing HTTP requests.

src/app/shared/okta/auth.interceptor.ts

TypeScript
 




xxxxxxxxxx
1
29


 
1
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
2
import { Observable, from } from 'rxjs';
3
import { OktaAuthService } from '@okta/okta-angular';
4
import { Injectable } from '@angular/core';
5
 
          
6
@Injectable()
7
export class AuthInterceptor implements HttpInterceptor {
8
 
          
9
  constructor(private oktaAuth: OktaAuthService) {
10
  }
11
 
          
12
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
13
    return from(this.handleAccess(request, next));
14
  }
15
 
          
16
  private async handleAccess(request: HttpRequest<any>, next: HttpHandler): Promise<HttpEvent<any>> {
17
    // Only add an access token to whitelisted origins
18
    const allowedOrigins = ['http://localhost'];
19
    if (allowedOrigins.some(url => request.urlWithParams.includes(url))) {
20
      const accessToken = await this.oktaAuth.getAccessToken();
21
      request = request.clone({
22
        setHeaders: {
23
          Authorization: 'Bearer ' + accessToken
24
        }
25
      });
26
    }
27
    return next.handle(request).toPromise();
28
  }
29
}



Note: You might notice that tokens are only added for http://localhost. You’ll need to modify the allowedOrigins array to include your production URL eventually.

Start your app again using ng serve, open a private/incognito window to http://localhost:4200, and you’ll see a Login button in the bottom left.

Login with Okta

Login with Okta

Click on it, and you’ll be redirected to Okta to log in.

Okta login

Okta login

Enter valid credentials, and you’ll be redirected back to your app. There will now be a Logout button, indicating that you’ve authenticated successfully.

Logout button now present

Logout button now present

Now that you’ve created a secure Angular 9 app let’s create a Spring Boot app to serve up data with a REST API.

Create a Spring Boot 2.2 App

The good folks at Pivotal created start.spring.io to help you create Spring Boot apps quickly with minimal fuss. This site is a Spring Boot app that has a REST API you can talk to with HTTPie.

Kotlin is an intriguing language for Spring developers because it reduces boilerplate code and allows succinct, effective code. Kotlin is 100% interoperable with Java, so you can continue to use the Java libraries and frameworks you know and love. Not only that, but Spring has first-class support for Kotlin.

Create a new Spring Boot app that uses Java 11, Kotlin, Gradle, and has the necessary dependencies to create a secure CRUD API.

Java
 




xxxxxxxxxx
1


 
1
http https://start.spring.io/starter.zip javaVersion==11 language==kotlin \
2
artifactId==notes-api groupId==com.okta.developer packageName==com.okta.developer.notes \
3
type==gradle-project dependencies==h2,data-jpa,data-rest,okta,web -d



Run this command in a terminal, and a notes-api.zip file will be downloaded. Expand it into the angular-spring-boot/notes-api directory.

Shell
 




xxxxxxxxxx
1


 
1
unzip notes-api.zip -d angular-spring-boot/notes-api



You can also use start.spring.io in your browser to create this same app.
Spring Initializer

Spring Initializer

Secure Spring Boot With Spring Security, OAuth 2.0, and OIDC

Because you selected Okta as a dependency, you’ll need to create an OIDC app for it to authenticate with Okta. You could use the client ID from your Angular app, but if you ever want to allow people to log in to your Spring Boot app, it’ll need its own OIDC app.

Log in to your Okta dashboard, then:

Your app’s settings should look like the following example:

App settings in Okta

App settings in Okta

You could copy your app’s OIDC settings to src/main/resources/application.properties:

Properties files
 




xxxxxxxxxx
1


 
1
okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default
2
okta.oauth2.client-id={yourClientId}
3
okta.oauth2.client-secret={yourClientSecret}



However, you should never store secrets in source control! To prevent you from checking secrets, you can use git-secrets.

For this example, copy your settings into a new okta.env file and ignore *.env in your notes-api/.gitignore file.

Properties files
 




xxxxxxxxxx
1


 
1
export OKTA_OAUTH2_ISSUER=https://{yourOktaDomain}/oauth2/default
2
export OKTA_OAUTH2_CLIENT_ID={yourClientId}
3
export OKTA_OAUTH2_CLIENT_SECRET={yourClientSecret}



After replacing the {…} placeholders with your values, run source okta.env to set these environment variables.

Then start your app using ./gradlew bootRun. Open http://localhost:8080 in a browser, and you’ll be redirected to Okta to sign in.

Note: If you don’t get prompted, it’s because you’re already logged in. Try it in an incognito window to see the full login flow.

Spring Boot as an OAuth 2.0 Resource Server

Your Spring Boot API is now secure, but it’s not configured to look for an Authorization header with an access token in it. You need to write some code to make your Spring Boot API an OAuth 2.0 resource server.

Create a SecurityConfiguration.kt class in the same directory as DemoApplication.kt:

Kotlin
 




xxxxxxxxxx
1
19


 
1
package com.okta.developer.notes
2
 
          
3
import org.springframework.security.config.annotation.web.builders.HttpSecurity
4
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
5
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
6
 
          
7
@EnableWebSecurity
8
class SecurityConfiguration : WebSecurityConfigurerAdapter() {
9
    override fun configure(http: HttpSecurity) {
10
        //@formatter:off
11
        http
12
            .authorizeRequests().anyRequest().authenticated()
13
                .and()
14
            .oauth2Login()
15
                .and()
16
            .oauth2ResourceServer().jwt()
17
        //@formatter:on
18
    }
19
}
4
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity



The oauth2Login() configuration is not necessary for this example to work. It’s only needed if you want to require authentication from a browser.

Add a Notes REST API With Spring Data REST

Start by creating a new Note entity in src/main/kotlin/…/notes/DemoApplication.kt.

Kotlin
 




xxxxxxxxxx
1
21


 
1
package com.okta.developer.notes
2
 
          
3
import com.fasterxml.jackson.annotation.JsonIgnore
4
import org.springframework.boot.autoconfigure.SpringBootApplication
5
import org.springframework.boot.runApplication
6
import javax.persistence.Entity
7
import javax.persistence.GeneratedValue
8
import javax.persistence.Id
9
 
          
10
@SpringBootApplication
11
class DemoApplication
12
 
          
13
fun main(args: Array<String>) {
14
    runApplication<DemoApplication>(*args)
15
}
16
 
          
17
@Entity
18
data class Note(@Id @GeneratedValue var id: Long? = null,
19
                var title: String? = null,
20
                var text: String? = null,
21
                @JsonIgnore var user: String? = null)
15
}



Kotlin’s data classes are built to hold data. By adding the data keyword, your class will get equals(), hashCode(), toString(), and a copy() function. The Type? = null syntax means the arguments are nullable when creating a new instance of the class.

Create a NotesRepository for persisting the data in your notes. Add the following lines of code just below your Note entity.

Kotlin
 




xxxxxxxxxx
1


 
1
@RepositoryRestResource
2
interface NotesRepository : JpaRepository<Note, Long>



The extends syntax differs from Java and is a lot more concise (a colon instead of extends). If your IDE doesn’t automatically add imports, you’ll need to add the following at the top of the file.

Kotlin
 




xxxxxxxxxx
1


 
1
import org.springframework.data.jpa.repository.JpaRepository
2
import org.springframework.data.rest.core.annotation.RepositoryRestResource



To automatically add the username to a note when it’s created, add a RepositoryEventHandler that is invoked before creating the record.

Kotlin
 




xxxxxxxxxx
1
11


 
1
@Component
2
@RepositoryEventHandler(Note::class)
3
class AddUserToNote {
4
 
          
5
    @HandleBeforeCreate
6
    fun handleCreate(note: Note) {
7
        val username: String =  SecurityContextHolder.getContext().getAuthentication().name
8
        println("Creating note: $note with user: $username")
9
        note.user = username
10
    }
11
}



The imports for this class are:

Kotlin
 




xxxxxxxxxx
1


 
1
import org.springframework.data.rest.core.annotation.HandleBeforeCreate
2
import org.springframework.data.rest.core.annotation.RepositoryEventHandler
3
import org.springframework.security.core.context.SecurityContextHolder
4
import org.springframework.stereotype.Component



Create a DataInitializer.kt class that populates the database with some default data on startup.

Kotlin
 




xxxxxxxxxx
1
17


 
1
package com.okta.developer.notes
2
 
          
3
import org.springframework.boot.ApplicationArguments
4
import org.springframework.boot.ApplicationRunner
5
import org.springframework.stereotype.Component
6
 
          
7
@Component
8
class DataInitializer(val repository: NotesRepository) : ApplicationRunner {
9
 
          
10
    @Throws(Exception::class)
11
    override fun run(args: ApplicationArguments) {
12
        listOf("Note 1", "Note 2", "Note 3").forEach {
13
            repository.save(Note(title = it, user = "user"))
14
        }
15
        repository.findAll().forEach { println(it) }
16
    }
17
}



Restart your Spring Boot app, and you should see the following printed to your console on startup.

Plain Text
 




xxxxxxxxxx
1


 
1
Note(id=1, title=Note 1, text=null, user=user)
2
Note(id=2, title=Note 2, text=null, user=user)
3
Note(id=3, title=Note 3, text=null, user=user)



Create a UserController.kt class (in the same directory as DemoApplication.kt) and use it to filter notes by the currently logged-in user. While you’re at it, add a /user endpoint that returns the user’s information.

Kotlin
 




xxxxxxxxxx
1
27


 
1
package com.okta.developer.notes
2
 
          
3
import org.springframework.security.core.annotation.AuthenticationPrincipal
4
import org.springframework.security.oauth2.core.oidc.user.OidcUser
5
import org.springframework.web.bind.annotation.GetMapping
6
import org.springframework.web.bind.annotation.RestController
7
import java.security.Principal
8
 
          
9
@RestController
10
class UserController(val repository: NotesRepository) {
11
 
          
12
    @GetMapping("/user/notes")
13
    fun notes(principal: Principal): List<Note> {
14
        println("Fetching notes for user: ${principal.name}")
15
        val notes = repository.findAllByUser(principal.name)
16
        if (notes.isEmpty()) {
17
            return listOf()
18
        } else {
19
            return notes
20
        }
21
    }
22
 
          
23
    @GetMapping("/user")
24
    fun user(@AuthenticationPrincipal user: OidcUser): OidcUser {
25
        return user;
26
    }
27
}



The findAllByUser() method doesn’t exist on NotesRepository, so you’ll need to add it. Thanks to Spring Data JPA, all you need to do is add the method definition to the interface, and it will handle generating the finder method in the implementation.

Kotlin
 




xxxxxxxxxx
1


 
1
interface NotesRepository : JpaRepository<Note, Long> {
2
    fun findAllByUser(name: String): List<Note>
3
}



To prevent conflicting paths with the REST endpoints created by @RepositoryRestResource, set the base path to /api in application.properties.

Properties files
 




xxxxxxxxxx
1


1
spring.data.rest.base-path=/api



Restart your Spring Boot app, navigate to http://localhost:8080/user, and you’ll see a whole plethora of details about your account. Opening http://localhost:8080/api/notes will show the default notes entered by the DataInitializer component.

Add a CORS Filter for Angular

In order for your Angular app (on port 4200) to communicate with your Spring Boot app (on port 8080), you have to enable CORS (cross-origin resource sharing). You can do this by giving your DemoApplication a body and defining a corsFilter bean inside it.

Kotlin
 




xxxxxxxxxx
1
24


 
1
import org.springframework.boot.web.servlet.FilterRegistrationBean
2
import org.springframework.context.annotation.Bean
3
import org.springframework.core.Ordered
4
import org.springframework.web.cors.CorsConfiguration
5
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
6
import org.springframework.web.filter.CorsFilter
7
 
          
8
@SpringBootApplication
9
class DemoApplication {
10
 
          
11
    @Bean
12
    fun simpleCorsFilter(): FilterRegistrationBean<CorsFilter> {
13
        val source = UrlBasedCorsConfigurationSource()
14
        val config = CorsConfiguration()
15
        config.allowCredentials = true
16
        config.allowedOrigins = listOf("http://localhost:4200")
17
        config.allowedMethods = listOf("*");
18
        config.allowedHeaders = listOf("*")
19
        source.registerCorsConfiguration("/**", config)
20
        val bean = FilterRegistrationBean(CorsFilter(source))
21
        bean.order = Ordered.HIGHEST_PRECEDENCE
22
        return bean
23
    }
24
}



Restart your Spring Boot app after adding this bean.

To see how your final DemoApplication file should look, you can view the completed version in GitHub.

Now that your API is working, it’s time to develop a UI for it with Angular 9!

Add a Notes CRUD Feature in Angular

Angular Schematics is a workflow tool that allows you to manipulate any project that has a package.json. Angular CLI is based on Schematics. OktaDev Schematics uses Schematics to update and add new files to projects. There’s even an Angular CRUD schematic!

Angular CRUD allows you to generate CRUD (create, read, update, and delete) screens and associated files from JSON.

In your Angular notes app, install angular-crud using npm:

Shell
 




xxxxxxxxxx
1


1
npm i -D angular-crud@1.0.0



Then, create a src/app/note directory.

Shell
 




xxxxxxxxxx
1


 
1
mkdir -p src/app/note



Then, create a model.json file in it that defines metadata that’s used when generating files.

JSON
 




xxxxxxxxxx
1
29


 
1
{
2
  "title": "Notes",
3
  "entity": "note",
4
  "api": {
5
    "url": "http://localhost:8080/api/notes"
6
  },
7
  "filter": [
8
    "title"
9
  ],
10
  "fields": [
11
    {
12
      "name": "id",
13
      "label": "Id",
14
      "isId": true,
15
      "readonly": true,
16
      "type": "number"
17
    },
18
    {
19
      "name": "title",
20
      "type": "string",
21
      "label": "Title"
22
    },
23
    {
24
      "name": "text",
25
      "type": "string",
26
      "label": "Text"
27
    }
28
  ]
29
}



Run the command below to generate CRUD screens.

Shell
 




xxxxxxxxxx
1


1
ng g angular-crud:crud-module note



You will see the following output.

Plain Text
 




xxxxxxxxxx
1
13


 
1
CREATE src/app/note/note-filter.ts (44 bytes)
2
CREATE src/app/note/note.module.ts (659 bytes)
3
CREATE src/app/note/note.routes.ts (346 bytes)
4
CREATE src/app/note/note.service.spec.ts (607 bytes)
5
CREATE src/app/note/note.service.ts (1744 bytes)
6
CREATE src/app/note/note.ts (69 bytes)
7
CREATE src/app/note/note-edit/note-edit.component.html (1097 bytes)
8
CREATE src/app/note/note-edit/note-edit.component.spec.ts (978 bytes)
9
CREATE src/app/note/note-edit/note-edit.component.ts (1493 bytes)
10
CREATE src/app/note/note-list/note-list.component.html (1716 bytes)
11
CREATE src/app/note/note-list/note-list.component.spec.ts (978 bytes)
12
CREATE src/app/note/note-list/note-list.component.ts (1091 bytes)
13
UPDATE src/app/app.module.ts (540 bytes)



This schematic creates a NotesModule, routes, a service to communicate with the API, and list/edit screens for viewing and editing notes. If you look at the src/app/note/note.routes.ts file, you’ll see the routes it creates.

TypeScript
 




xxxxxxxxxx
1
14


 
1
import { Routes } from '@angular/router';
2
import { NoteListComponent } from './note-list/note-list.component';
3
import { NoteEditComponent } from './note-edit/note-edit.component';
4
 
          
5
export const NOTE_ROUTES: Routes = [
6
  {
7
    path: 'notes',
8
    component: NoteListComponent
9
  },
10
  {
11
    path: 'notes/:id',
12
    component: NoteEditComponent
13
  }
14
];



Add a link to the NoteListComponent in src/app/home/home.component.html.

HTML
 




xxxxxxxxxx
1


 
1
<div>
2
  <button *ngIf="!isAuthenticated" (click)="oktaAuth.loginRedirect()">Login</button>
3
  <p><a routerLink="/notes" *ngIf="isAuthenticated">View Notes</a></p>
4
  <button *ngIf="isAuthenticated" (click)="oktaAuth.logout()">Logout</button>
5
</div>



Change src/app/app.component.html to be as simple as it can be.

HTML
 




xxxxxxxxxx
1


 
1
<h1>{{ title }} app is running!</h1>
2
 
          
3
<router-outlet></router-outlet>



If you want npm test to pass after modifying this template, you’ll need to change app.component.spec.ts to look for querySelector('h1') instead of querySelector('.content span').


Run ng serve (and make sure your Spring Boot app is running too).

Notes app is running

Notes app is running

Log in, and you should see a View Notes link.

Adding "View Notes" link

Adding View notes link

Click on the link, and you’ll see a list screen like the one below. No notes are displayed because you haven’t created any notes that are tied to your user.

Notes list

Notes list

Click on the New link to add a new note.

Adding a new note

Addng a new note

Add a new note, and you’ll see a message like this in your backend console.

Plain Text
 




xxxxxxxxxx
1


1
Creating note: Note(id=null, title=1st note, text=Wahoo!, user=null) with user: matt.raible@okta.com



You still won’t see notes in the list. You need to change the NoteService to call the /user/notes endpoint to get your notes.

TypeScript
 




xxxxxxxxxx
1


 
1
find(filter: NoteFilter): Observable<Note[]> {
2
  const params = {
3
    title: filter.title,
4
  };
5
  const userNotes = 'http://localhost:8080/user/notes';
6
  return this.http.get<Note[]>(userNotes, {params, headers});
7
}



Now, you’ll see your notes listed. Nice work!

Notes list (functional)

Notes list (functional)

You might be wondering how the NoteListComponent works. It loads the user’s notes from the NoteService when the component initializes and also contains select() and delete() methods. The reason it’s able to talk to your secured Spring Boot API is that the aforementioned AuthInterceptor adds an access token to the request.

src/app/note/note-list/note-list.component.ts

TypeScript
 




xxxxxxxxxx
1
49


 
1
import { Component, OnInit } from '@angular/core';
2
import { NoteFilter } from '../note-filter';
3
import { NoteService } from '../note.service';
4
import { Note } from '../note';
5
 
          
6
@Component({
7
  selector: 'app-note',
8
  templateUrl: 'note-list.component.html'
9
})
10
export class NoteListComponent implements OnInit {
11
 
          
12
  filter = new NoteFilter();
13
  selectedNote: Note;
14
  feedback: any = {};
15
 
          
16
  get noteList(): Note[] {
17
    return this.noteService.noteList;
18
  }
19
 
          
20
  constructor(private noteService: NoteService) {
21
  }
22
 
          
23
  ngOnInit() {
24
    this.search();
25
  }
26
 
          
27
  search(): void {
28
    this.noteService.load(this.filter);
29
  }
30
 
          
31
  select(selected: Note): void {
32
    this.selectedNote = selected;
33
  }
34
 
          
35
  delete(note: Note): void {
36
    if (confirm('Are you sure?')) {
37
      this.noteService.delete(note).subscribe(() => {
38
          this.feedback = {type: 'success', message: 'Delete was successful!'};
39
          setTimeout(() => {
40
            this.search();
41
          }, 1000);
42
        },
43
        err => {
44
          this.feedback = {type: 'warning', message: 'Error deleting.'};
45
        }
46
      );
47
    }
48
  }
49
}



The Edit link in this component’s template links to the NoteEditComponent.

HTML
 




xxxxxxxxxx
1


 
1
<a [routerLink]="['../notes', item.id ]" class="btn btn-secondary">Edit</a>



The NoteEditComponent has methods for loading a note, saving a note, and canceling.

TypeScript
 




xxxxxxxxxx
1
63


 
1
import { Component, OnInit } from '@angular/core';
2
import { ActivatedRoute, Router } from '@angular/router';
3
import { NoteService } from '../note.service';
4
import { Note } from '../note';
5
import { map, switchMap } from 'rxjs/operators';
6
import { of } from 'rxjs';
7
 
          
8
@Component({
9
  selector: 'app-note-edit',
10
  templateUrl: './note-edit.component.html'
11
})
12
export class NoteEditComponent implements OnInit {
13
 
          
14
  id: string;
15
  note: Note;
16
  feedback: any = {};
17
 
          
18
  constructor(
19
    private route: ActivatedRoute,
20
    private router: Router,
21
    private noteService: NoteService) {
22
  }
23
 
          
24
  ngOnInit() {
25
    this
26
      .route
27
      .params
28
      .pipe(
29
        map(p => p.id),
30
        switchMap(id => {
31
          if (id === 'new') { return of(new Note()); }
32
          return this.noteService.findById(id);
33
        })
34
      )
35
      .subscribe(note => {
36
          this.note = note;
37
          this.feedback = {};
38
        },
39
        err => {
40
          this.feedback = {type: 'warning', message: 'Error loading'};
41
        }
42
      );
43
  }
44
 
          
45
  save() {
46
    this.noteService.save(this.note).subscribe(
47
      note => {
48
        this.note = note;
49
        this.feedback = {type: 'success', message: 'Save was successful!'};
50
        setTimeout(() => {
51
          this.router.navigate(['/notes']);
52
        }, 1000);
53
      },
54
      err => {
55
        this.feedback = {type: 'warning', message: 'Error saving'};
56
      }
57
    );
58
  }
59
 
          
60
  cancel() {
61
    this.router.navigate(['/notes']);
62
  }
63
}



Fix the Note Edit Feature

One of the problems with the NoteEditComponent is it assumes the API returns an ID. Since Spring Data REST uses HATEOS by default, it returns links instead of IDs. You can change this default to return IDs by creating a RestConfiguration class in your Spring Boot app. You might notice you can also configure the base path in this class, instead of in application.properties.

Kotlin
 




xxxxxxxxxx
1
13


 
1
package com.okta.developer.notes
2
 
          
3
import org.springframework.context.annotation.Configuration
4
import org.springframework.data.rest.core.config.RepositoryRestConfiguration
5
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer
6
 
          
7
@Configuration
8
class RestConfiguration : RepositoryRestConfigurer {
9
   override fun configureRepositoryRestConfiguration(config: RepositoryRestConfiguration?) {
10
       config?.exposeIdsFor(Note::class.java)
11
       config?.setBasePath("/api")
12
   }
13
}



Another option is to modify the Angular side of things. Since the ID is passed into the NoteEditComponent, you can set it as a local variable, then set it on the note after it’s returned. Here’s a diff of what changes need to be made in notes/src/app/note/note-edit/note-edit.component.ts.

diff
 




xxxxxxxxxx
1
24


 
1
--- a/notes/src/app/note/note-edit/note-edit.component.ts
2
+++ b/notes/src/app/note/note-edit/note-edit.component.ts
3
@@ -30,11 +29,13 @@ export class NoteEditComponent implements OnInit {
4
        map(p => p.id),
5
        switchMap(id => {
6
          if (id === 'new') { return of(new Note()); }
7
+          this.id = id;
8
          return this.noteService.findById(id);
9
        })
10
      )
11
      .subscribe(note => {
12
          this.note = note;
13
+          this.note.id = +note.id;
14
          this.feedback = {};
15
        },
16
        err => {
17
@@ -47,6 +48,7 @@ export class NoteEditComponent implements OnInit {
18
    this.noteService.save(this.note).subscribe(
19
      note => {
20
        this.note = note;
21
+        this.note.id = +this.id;
22
        this.feedback = {type: 'success', message: 'Save was successful!'};
23
        setTimeout(() => {
24
          this.router.navigate(['/notes']);



In the final example for this post, I opted to return IDs from my Spring Boot API.

Lock Down Spring Boot With Recommended Security Practices

In 10 Excellent Ways to Secure Your Spring Boot Application, I recommended a few Spring Boot-specific items:

  1. Use HTTPS in Production.

  2. Enable Cross-Site Request Forgery (CSRF) Protection.

  3. Use a Content Security Policy (CSP) to Prevent XSS Attacks.

  4. Use OpenID Connect for Authentication.

You’ve already implemented #4 with Okta, but what about the others?

You can use mkcert to generate local, valid TLS certificates. To force HTTPS, you just need to configure Spring Security. I like just to do it in production, so I don’t need to install certificates in development.

CSRF protection and a CSP can be configured with Spring Security.

Modify your SecurityConfiguration class with these security enhancements.

Kotlin
 




xxxxxxxxxx
1
23


 
1
class SecurityConfiguration : WebSecurityConfigurerAdapter() {
2
    override fun configure(http: HttpSecurity) {
3
        //@formatter:off
4
        http
5
            .authorizeRequests().anyRequest().authenticated()
6
                .and()
7
            .oauth2Login()
8
                .and()
9
            .oauth2ResourceServer().jwt()
10
 
          
11
        http.requiresChannel()
12
            .requestMatchers(RequestMatcher {
13
                r -> r.getHeader("X-Forwarded-Proto") != null
14
            }).requiresSecure() 
15
 
          
16
        http.csrf()
17
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); 
18
 
          
19
        http.headers()
20
            .contentSecurityPolicy("script-src 'self'; report-to /csp-report-endpoint/"); 
21
       //@formatter:on
22
    }
23
}


1. Force HTTPS in production
2. Configure the CSRF Cookie so it can be read by JavaScript
3 Configure a CSP that only allows local scripts


Angular’s HttpClient has built-in support for the client-side half of the CSRF protection. It’ll read the cookie sent by Spring Boot and return it in an X-XSRF-TOKEN header. You can read more about this in Angular’s Security docs.

In this particular example, the CSP won’t be used since Angular is a separate app. However, if you were to include the Angular app in your Spring Boot artifact, it’d come in handy.

Once you’ve deployed your Spring Boot app to a public URL, you can test your CSP headers are working with securityheaders.com.

Note:
Once you’ve deployed your Spring Boot app to a public URL, you can test your CSP headers are working with securityheaders.com.

Learn More about Angular, Spring Boot, and Kotlin

In this tutorial, I showed you how to create an Angular 9 app, a Spring Boot 2.2 app, and how to secure communication between them with OAuth 2.0 and OIDC. You used Kotlin on the backend; a language loved by many. You used Angular Schematics to generate code for authentication and CRUD, improving your efficiency as a developer.

This tutorial did not show you how to make your Angular app look good, add validation, or how to deploy it to a public server. I’ll tackle those topics in an upcoming post. In the meantime, you can see a previous tutorial I wrote to see how this app might look with Angular Material.

You can find the source code for the completed application at oktadeveloper/okta-spring-boot-2-angular-9-example.

Our blog has a bunch of Angular, Spring Boot, and Kotlin tutorials. Here are some I recommend:

Changelog:


Further Reading

Topics:
angular, angular 9, angular app development, angular js, crud, spring boot, spring boot 2.2

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}