Académique Documents
Professionnel Documents
Culture Documents
Matt Raible
Technology moves fast these days. It can be challenging to keep up with the latest
trends as well as new releases of your favorite projects. I’m here to help! Spring Boot
and Angular are two of my favorite projects, so I figured I’d write y’all a guide to show
you how to build and secure a basic app using their latest and greatest releases.
In Spring Boot, the most significant change in 2.0 is its new web framework: Spring
WebFlux. In Angular 5.0, we get a new HttpClient on the table. This class
replaces Http, and is a bit easier to use, with less boilerplate code. Today, I’m not going
to explore Spring WebFlux, because we still have some work to do before we can
support in with the Okta Spring Boot Starter.
The good news is our Angular SDK works well with Angular 5, so I’ll be showing how to
use it in this blog post. Speaking of Angular, did you know that Angular has one of the
most dramatic increases in questions on Stack Overflow? You might think this means a
lot of people have issues with Angular. I like to think that there’s a lot of people using it,
and developers often have questions when using a new technology. It’s a definite sign
of a healthy community. You rarely see a lot of questions on Stack Overflow for a dying
technology.
This article describes how to build a simple CRUD application that displays a list of cool
cars. It’ll allow you to edit the list, and it’ll show an animated gif from GIPHY that
matches the car’s name. You’ll also learn how to secure your application using Okta’s
Spring Boot starter and Angular SDK.
You will need Java 8 and Node.js 8 installed to complete this tutorial.
After downloading demo.zip from start.spring.io, expand it and copy the demo directory to
your app-holder directory. Rename demo to server. Open the project in your favorite IDE
and create a Car.java file in the src/main/java/com/okta/developer/demo directory.
You can use Lombok’s annotations to reduce boilerplate code.
package com.okta.developer.demo;
import lombok.*;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.Entity;
@Entity
@Getter @Setter
@NoArgsConstructor
@ToString @EqualsAndHashCode
@Id @GeneratedValue
Create a CarRepository class to perform CRUD (create, read, update, and delete) on
the Car entity.
package com.okta.developer.demo;
import org.springframework.data.jpa.repository.JpaRepository;
import
org.springframework.data.rest.core.annotation.RepositoryRestReso
urce;
@RepositoryRestResource
package com.okta.developer.demo;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import
org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.stream.Stream;
@SpringBootApplication
SpringApplication.run(DemoApplication.class, args);
}
@Bean
car.setName(name);
repository.save(car);
});
repository.findAll().forEach(System.out::println);
};
If you start your app (using ./mvnw spring-boot:run) after adding this code, you’ll see
the list of cars displayed in your console on startup.
Car(id=1, name=Ferrari)
Car(id=2, name=Jaguar)
Car(id=3, name=Porsche)
Car(id=4, name=Lamborghini)
Car(id=5, name=Bugatti)
package com.okta.developer.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collection;
import java.util.stream.Collectors;
@RestController
class CoolCarController {
this.repository = repository;
@GetMapping("/cool-cars")
return repository.findAll().stream()
.filter(this::isCool)
.collect(Collectors.toList());
!car.getName().equals("Yugo GV");
If you restart your server app and hit localhost:8080/cool-cars with your browser, or a
command-line client, you should see the filtered list of cars.
http localhost:8080/cool-cars
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
"id": 1,
"name": "Ferrari"
},
{
"id": 2,
"name": "Jaguar"
},
"id": 3,
"name": "Porsche"
},
"id": 4,
"name": "Lamborghini"
},
"id": 5,
"name": "Bugatti"
Create a new project in the umbrella directory you created. Again, mine is named okta-
spring-boot-2-angular-5-example.
ng new client
After the client is created, navigate into its directory and install Angular Material.
cd client
npm install --save-exact @angular/material@5.2.4
@angular/cdk@5.2.4
You’ll use Angular Material’s components to make the UI look better, especially on
mobile phones. If you’d like to learn more about Angular Material,
see https://material.angular.io. It has extensive documentation on its various
components and how to use them.
Build a Car List Page
Use Angular CLI to generate a car service that can talk to the Cool Cars API.
ng g s car
mkdir -p src/app/shared/car
mv src/app/car.service.* src/app/shared/car/.
Update the code in car.service.ts to fetch the list of cars from the server.
@Injectable()
getAll(): Observable<any> {
return this.http.get('//localhost:8080/cool-cars');
}
TIP: If you’re using using Angular 6+, you can make this code work by installing rxjs-
compat with npm i rxjs-compat.
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [CarService],
bootstrap: [AppComponent]
})
ng g c car-list
cars: Array<any>;
ngOnInit() {
this.carService.getAll().subscribe(data => {
this.cars = data;
});
<h2>Car List</h2>
{{car.name}}
</div>
<div style="text-align:center">
<h1>Welcome to {{title}}!</h1>
</div>
<app-car-list></app-car-list>
Start the client application using ng serve. Open your favorite browser to
http://localhost:4200. You won’t see the car list just yet, and if you open your developer
console, you’ll see why.
This error happens because you haven’t enabled CORS (Cross-Origin Resource
Sharing) on the server.
import org.springframework.web.bind.annotation.CrossOrigin;
...
@GetMapping("/cool-cars")
@CrossOrigin(origins = "http://localhost:4200")
return repository.findAll().stream()
.filter(this::isCool)
.collect(Collectors.toList());
Also, add it to CarRepository so you can communicate with its endpoints when
adding/deleting/editing.
@RepositoryRestResource
@CrossOrigin(origins = "http://localhost:4200")
Restart the server, refresh the client, and you should see the list of cars in your browser.
...
imports: [
BrowserModule,
HttpClientModule,
BrowserAnimationsModule,
MatButtonModule,
MatCardModule,
MatInputModule,
MatListModule,
MatToolbarModule
],
...
})
<mat-toolbar color="primary">
<span>Welcome to {{title}}!</span>
</mat-toolbar>
<app-car-list></app-car-list>
<mat-card>
<mat-card-header>Car List</mat-card-header>
<mat-card-content>
<mat-list>
<h3 mat-line>{{car.name}}</h3>
</mat-list-item>
</mat-list>
</mat-card-content>
</mat-card>
@import "~@angular/material/prebuilt-themes/pink-bluegrey.css";
@import
'https://fonts.googleapis.com/icon?family=Material+Icons';
body {
margin: 0;
If you run your client with ng serve and navigate to http://localhost:4200, you’ll see the
list of cars, but no images associated with them.
Add Animated GIFs with Giphy
To add a giphyUrl property to cars,
create client/src/app/shared/giphy/giphy.service.ts and populate it with the code
below.
import 'rxjs/add/operator/map';
@Injectable()
giphyApi =
'//api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&limit=1&q=
';
get(searchTerm) {
if (response.data.length > 0) {
return response.data[0].images.original.url;
} else {
return
'https://media.giphy.com/media/YaOxRsmrv9IeA/giphy.gif'; //
dancing cat for 404
});
}
Add GiphyService as a provider in client/src/app/app.module.ts.
@NgModule({
...
bootstrap: [AppComponent]
})
cars: Array<any>;
ngOnInit() {
this.carService.getAll().subscribe(data => {
this.cars = data;
this.giphyService.get(car.name).subscribe(url =>
car.giphyUrl = url);
}
});
Now your browser should show you the list of car names, along with an avatar image
beside them.
ng g c car-edit
@Injectable()
getAll(): Observable<any> {
get(id: string) {
if (car['href']) {
} else {
return result;
remove(href: string) {
return this.http.delete(href);
<mat-card>
<mat-card-header>Car List</mat-card-header>
<mat-card-content>
<mat-list>
<h3 mat-line>
</h3>
</mat-list-item>
</mat-list>
</mat-card-content>
</mat-card>
path: 'car-list',
component: CarListComponent
},
{
path: 'car-add',
component: CarEditComponent
},
path: 'car-edit/:id',
component: CarEditComponent
];
@NgModule({
...
imports: [
...
FormsModule,
RouterModule.forRoot(appRoutes)
],
...
})
@Component({
selector: 'app-car-edit',
templateUrl: './car-edit.component.html',
styleUrls: ['./car-edit.component.css']
})
sub: Subscription;
ngOnInit() {
const id = params['id'];
if (id) {
if (car) {
this.car = car;
this.car.href = car._links.self.href;
this.giphyService.get(car.name).subscribe(url =>
car.giphyUrl = url);
} else {
this.gotoList();
});
});
ngOnDestroy() {
this.sub.unsubscribe();
gotoList() {
this.router.navigate(['/car-list']);
}
save(form: NgForm) {
this.carService.save(form).subscribe(result => {
this.gotoList();
remove(href) {
this.carService.remove(href).subscribe(result => {
this.gotoList();
<mat-card>
<mat-card-header>
</mat-card-header>
<mat-card-content>
<mat-form-field>
<input matInput placeholder="Car Name"
[(ngModel)]="car.name"
</mat-form-field>
</mat-card-content>
<mat-card-actions>
[disabled]="!carForm.form.valid">Save</button>
*ngIf="car.href" type="button">Delete</button>
</mat-card-actions>
<mat-card-footer>
<div class="giphy">
</div>
</mat-card-footer>
</form>
</mat-card>
Put a little padding around the image by adding the following CSS
to client/src/app/car-edit/car-edit.component.css.
.giphy {
margin: 10px;
}
<mat-toolbar color="primary">
<span>Welcome to {{title}}!</span>
</mat-toolbar>
<router-outlet></router-outlet>
After you make all these changes, you should be able to add, edit, or delete any cars.
Below is a screenshot that shows the list with the add button.
The following screenshot shows what it looks like to edit a car that you’ve added.
Add Authentication with Okta
Add authentication with Okta is a nifty feature you can add to this application. Knowing
who the person is can come in handy if you want to add auditing, or personalize your
application (with a rating feature for example).
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-
autoconfigure</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
Now you need to configure the server to use Okta for authentication. You’ll need to
create an OIDC app in Okta for that.
security:
oauth2:
client:
access-token-uri:
https://{yourOktaDomain}/oauth2/default/v1/token
user-authorization-uri:
https://{yourOktaDomain}/oauth2/default/v1/authorize
client-id: {clientId}
resource:
user-info-uri:
https://{yourOktaDomain}/oauth2/default/v1/userinfo
token-info-uri:
https://{yourOktaDomain}/oauth2/default/v1/introspect
prefer-token-info: false
Update server/src/main/java/com/okta/developer/demo/DemoApplication.java to
enable it as a resource server.
import
org.springframework.security.oauth2.config.annotation.web.config
uration.EnableResourceServer;
@EnableResourceServer
@SpringBootApplication
After making these changes, you should be able to restart your app and see access
denied when you try to navigate to http://localhost:8080.
It’s nice that your server is locked down, but now you need to configure your client to
talk to it. This is where Okta’s Angular support comes in handy.
const config = {
issuer: 'https://{yourOktaDomain}/oauth2/default',
redirectUri: 'http://localhost:4200/implicit/callback',
clientId: '{clientId}'
};
In this same file, you’ll also need to add a new route for the redirectUri that points to
the OktaCallbackComponent.
...
path: 'implicit/callback',
component: OktaCallbackComponent
];
@NgModule({
...
imports: [
...
OktaAuthModule.initAuth(config)
],
...
})
These are the three steps you need to set up an Angular app to use Okta. To make it
easy to add a bearer token to HTTP requests, you can use an HttpInterceptor.
import 'rxjs/add/observable/fromPromise';
@Injectable()
return Observable.fromPromise(this.handleAccess(request,
next));
request = request.clone({
setHeaders: {
});
return next.handle(request).toPromise();
@NgModule({
...
})
<mat-toolbar color="primary">
<span>Welcome to {{title}}!</span>
<span class="toolbar-spacer"></span>
(click)="oktaAuth.logout()">Logout
</button>
</mat-toolbar>
<mat-card *ngIf="!isAuthenticated">
<mat-card-content>
(click)="oktaAuth.loginRedirect()">Login
</button>
</mat-card-content>
</mat-card>
<router-outlet></router-outlet>
You might notice there’s a span with a toolbar-spacer class. To make that work as
expected, update client/src/app/app.component.css to have the following class.
.toolbar-spacer {
flex: 1 1 auto;
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
title = 'app';
isAuthenticated: boolean;
async ngOnInit() {
this.isAuthenticated = await
this.oktaAuth.isAuthenticated();
);
Now if you restart your client, you should see a login button.
Notice that this shows elements from the car-list component. To fix this, you can create
a home component and make it the default route.
ng g c home
path: 'home',
component: HomeComponent
},
...
<mat-card>
<mat-card-content>
(click)="oktaAuth.loginRedirect()">Login
</button>
[routerLink]="['/car-list']">Car List
</button>
</mat-card-content>
</mat-card>
isAuthenticated: boolean;
async ngOnInit() {
this.isAuthenticated = await
this.oktaAuth.isAuthenticated();
this.oktaAuth.$authenticationState.subscribe(
);
<mat-toolbar color="primary">
<span>Welcome to {{title}}!</span>
<span class="toolbar-spacer"></span>
</button>
</mat-toolbar>
<router-outlet></router-outlet>
Now you should be able to open your browser to http://localhost:4200 and click on the
Login button. If you’ve configured everything correctly, you’ll be redirected to Okta to log
in.
Enter the credentials you used to sign up for an account, and you should be redirected
back to your app. However, your list of cars won’t load because of a CORS error. This
happens because Spring’s @CrossOrigin doesn’t work well with Spring Security.
import
org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.core.Ordered;
import org.springframework.web.cors.CorsConfiguration;
import
org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Collections;
...
@Bean
@SuppressWarnings("unchecked")
config.setAllowCredentials(true);
config.setAllowedOrigins(Collections.singletonList("http://local
host:4200"));
config.setAllowedMethods(Collections.singletonList("*"));
config.setAllowedHeaders(Collections.singletonList("*"));
source.registerCorsConfiguration("/**", config);
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
I’ve written a number of Spring Boot and Angular tutorials in the past, and I’ve recently
updated them for Angular 5.
Build a Basic CRUD App with Angular 7.0 and Spring Boot 2.1
Bootiful Development with Spring Boot and Angular
Build a Secure Notes Application with Kotlin, TypeScript, and Okta
Angular Authentication with OpenID Connect and Okta in 20 Minutes
Build an Angular App with Okta’s Sign-In Widget in 15 Minutes
If you have any questions, please don’t hesitate to leave a comment below, or ask us on
our Okta Developer Forums. Follow us on Twitter if you want to be notified when we
publish new blog posts.
Changelog:
Apr 9, 2018: Updated to use Okta Angular 1.0.0, Spring Boot 2.0.1, and Angular CLI
1.7.4 (with Angular 5.2.9). See the code changes in the example app on GitHub.
Changes to this article can be viewed in okta/okta.github.io#1938.
Mar 5, 2018: Updated to use Spring Boot 2.0 and Angular CLI 1.7.2 (with Angular
5.2.7). See the code changes in the example app on GitHub. Changes to this article can
be viewed in okta/okta.github.io#1806.