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

The 101: Spring Boot and Angular Deployment

DZone 's Guide to

The 101: Spring Boot and Angular Deployment

Take a look at this tutorial that demonstates Spring Boot and Angular deployment to Heroku as well as some tips on keeping them secure.

· Java Zone ·
Free Resource

Angular + Spring Boot is a very popular combination for frontend and backend frameworks. In the past, I’ve done several tutorials on how to combine them; you can keep them as separate apps, or make them into a single artifact. But I haven’t yet talked about deployment.

I’m often asked the best way to do Angular deployment, so I’ve decided to write up a tutorial showing several options. First, I’m going to talk about how to deploy a Spring Boot app to Heroku. Next, I’ll talk about what you need to know to deploy a separate Angular app to Heroku.

You can find many different how-tos and info in the community on deploying Spring Boot apps, so I’m going to leave the Spring Boot API on Heroku and give some other Angular deployment options, such as Netlify, AWS S3, and Firebase.

Table of Contents

You can also watch this tutorial as a screencast.


Create an Angular + Spring Boot App

Since this tutorial is more about deployment than app creation, you can start with an existing Angular + Spring Boot app that I created previously. It’s a note-taking app that uses Kotlin and Spring Boot 2.2 on the backend and Angular 9 on the frontend. It’s secured with OpenID Connect (OIDC). If you’d like to see how I built it, you can read the following tutorials:

One of the slick features of this app is its full-featured data table that allows sorting, searching, and pagination. This feature is powered by NG Bootstrap and Spring Data JPA. Below is a screenshot:

data table screenshop

Clone the application into an okta-angular-deployment-example directory.

Shell
 




xxxxxxxxxx
1


 
1
git clone https://github.com/oktadeveloper/okta-angular-bootstrap-example.git \
2
 okta-angular-deployment-example


Prerequisites:

Secure Your Angular + Spring Boot App with OIDC

To begin, you’ll need to create a Heroku account. If you already have a Heroku account, log in to it. Once you’re logged in, create a new app. I named mine bootiful-angular.

create new app

After creating your app, click on the Resources tab and add the Okta add-on.


add okta add-on

If you haven’t entered a credit card for your Heroku account, you will receive an error. This is because Heroku requires you to have a credit card on file to use any of their add-ons, even for free ones. This is part of Heroku’s assurance to guard against misuse (real person, real credit card, etc.). I think this is a good security practice. Simply add a credit card to continue.

Click Provision and wait 20-30 seconds while your Okta account is created and OIDC apps are registered. Now go to your app’s Settings tab and click the Reveal Config Vars button. The Config Vars displayed are the environment variables you can use to configure both Angular and Spring Boot for OIDC authentication.
config vars

Create an okta.env file in the okta-angular-deployment-example/notes-api directory and copy the config vars into it, where $OKTA_* is the value from Heroku.

PowerShell
 




xxxxxxxxxx
1


 
1
export OKTA_OAUTH2_ISSUER=$OKTA_OAUTH2_ISSUER
2
export OKTA_OAUTH2_CLIENT_ID=$OKTA_OAUTH2_CLIENT_ID_WEB
3
export OKTA_OAUTH2_CLIENT_SECRET=$OKTA_OAUTH2_CLIENT_SECRET_WEB


Start your Spring Boot app by navigating to the notes-api directory, sourcing this file, and running ./gradlew bootRun.

Shell
 




xxxxxxxxxx
1


 
1
source okta.env
2
./gradlew bootRun


Environment Variables in IntelliJ IDEA

If you’re using IntelliJ IDEA, you can copy the contents of okta.env and paste its values as environment variables. Edit the DemoApplication configuration and click on the Browse icon on the right-side of Environment variables.


environment variables

Next, click the paste icon. You’ll need to delete export in the Name column. Now you can run your Spring Boot app with Okta from IDEA!

environment variables

Next, configure Angular for OIDC authentication by modifying its auth-routing.module.ts to use the generated issuer, client ID, and update the callback URL.

notes/src/app/auth-routing.module.ts

TypeScript
 




xxxxxxxxxx
1
14


 
1
const oktaConfig = {
2
  issuer: '$OKTA_OAUTH2_ISSUER',
3
  redirectUri: window.location.origin + '/callback',
4
  clientId: '$OKTA_OAUTH2_CLIENT_ID_SPA',
5
  pkce: true
6
};
7
 
          
8
const routes: Routes = [
9
  ...
10
  {
11
    path: '/callback',
12
    component: OktaCallbackComponent
13
  }
14
];


Install your Angular app’s dependencies and start it.

Shell
 




xxxxxxxxxx
1


1
npm i
2
ng serve


Open http://localhost:4200 in your browser.

http://localhost:4200

Click the Login button in the top right corner. You should be logged in straightaway, since you’re already logged in to Okta. If you want to see the full authentication flow, log out, or try it in a private window. You can use the $OKTA_ADMIN_EMAIL and $OKTA_ADMIN_PASSWORD from your Heroku config variables for credentials. Create a note to make sure everything works.

notes list

Commit your progress to Git from the top-level okta-angular-deployment-example directory.

Shell
 




xxxxxxxxxx
1


 
1
git commit -am "Add Okta OIDC Configuration"


Prepare Angular + Spring Boot for Production

There are a couple of things you should do to make your app ready for production.

  1. Make sure you’re using the latest releases
  2. Configure production URLs
  3. Use PostgreSQL for the production database

You’re going to want to continue to develop locally—so you’ll want a production mode as well as a development mode.

Update Spring Boot and Angular Dependencies

I’m the type of developer that likes to use the latest releases of open source libraries. I do this to take advantage of new features, performance optimizations, and security fixes.

There’s a Gradle Use Latest Versions Plugin that provides a task to update dependencies to the latest version. Configure it by adding the following to the plugins block at the top of notes-api/build.gradle.kts.

Kotlin
 




xxxxxxxxxx
1


 
1
plugins {
2
    id("se.patrikerdes.use-latest-versions") version "0.2.13"
3
    id("com.github.ben-manes.versions") version "0.28.0"
4
    ...
5
}


For compatibility with Spring Boot 2.3, you’ll need to update the Gradle Wrapper to use Gradle 6.3+.

Shell
 




xxxxxxxxxx
1


 
1
./gradlew wrapper --gradle-version=6.5 --distribution-type=bin


Then run the following command in the notes-api directory to update your dependencies to the latest released versions.

Shell
 




xxxxxxxxxx
1


 
1
./gradlew useLatestVersions


You can verify everything still works by running ./gradlew bootRun and navigating to http://localhost:8080/api/notes. You should be redirected to Okta to log in, then back to your app.

If your app fails to start, you need to run source okta.env first.

For the Angular client, you can use npm-check-updates to upgrade npm dependencies.

Shell
 




xxxxxxxxxx
1


 
1
npm i -g npm-check-updates
2
ncu -u


At the time of this writing, this will upgrade Angular to version 9.1.9 and TypeScript to version 3.9.3. Angular 9 supports TypeScript versions >=3.6.4 and <3.9.0, so you’ll need to change package.json to specify TypeScript 3.8.3.

JSON
 




xxxxxxxxxx
1


1
"typescript": "~3.8.3"


Then run the following commands in the notes directory:

Shell
 




xxxxxxxxxx
1


1
npm i
2
npm audit fix
3
ng serve


Confirm you can still log in at http://localhost:4200.

Commit all your changes to source control.

Shell
 




xxxxxxxxxx
1


1
git commit -am "Update dependencies to latest versions"


Configure Production URLs

There are a few places where localhost is hard-coded:

  1. notes-api/src/main/kotlin/…/DemoApplication.kt has http://localhost:4200
  2. notes/src/app/shared/okta/auth-interceptor.ts has http://localhost
  3. notes/src/app/note/note.service.ts has http://localhost:8080

You need to change Spring Boot’s code so other origins can make CORS requests too. Angular’s code needs updating so access tokens will be sent to production URLs while API requests are sent to the correct endpoint.

Open the root directory in your favorite IDE and configure it so it loads notes-api as a Gradle project. Open DemoApplication.kt and change the simpleCorsFilter bean so it configures the allowed origins from your Spring environment.

notes-api/src/main/kotlin/com/okta/developer/notes/DemoApplication.kt

Kotlin
 




xxxxxxxxxx
1
22


 
1
import org.springframework.beans.factory.annotation.Value
2
 
          
3
@SpringBootApplication
4
class DemoApplication {
5
 
          
6
    @Value("#{ @environment['allowed.origins'] ?: {} }")
7
    private lateinit var allowedOrigins: List<String>
8
 
          
9
    @Bean
10
    fun simpleCorsFilter(): FilterRegistrationBean<CorsFilter> {
11
        val source = UrlBasedCorsConfigurationSource()
12
        val config = CorsConfiguration()
13
        config.allowCredentials = true
14
        config.allowedOrigins = allowedOrigins
15
        config.allowedMethods = listOf("*");
16
        config.allowedHeaders = listOf("*")
17
        source.registerCorsConfiguration("/**", config)
18
        val bean = FilterRegistrationBean(CorsFilter(source))
19
        bean.order = Ordered.HIGHEST_PRECEDENCE
20
        return bean
21
    }
22
}


Define the allowed.origins property in notes-api/src/main/resources/application.properties.

Properties files
 




xxxxxxxxxx
1


 
1
allowed.origins=http://localhost:4200


Angular has an environment concept built-in. When you run ng build --prod to create a production build, it replaces environment.ts with environment.prod.ts.

Open environment.ts and add an apiUrl variable for development.

notes/src/environments/environment.ts

TypeScript
 




xxxxxxxxxx
1


 
1
export const environment = {
2
  production: false,
3
  apiUrl: 'http://localhost:8080'
4
};


Edit environment.prod.ts to point to your production Heroku URL. Be sure to replace bootiful-angular with your app’s name.

notes/src/environments/environment.prod.ts

TypeScript
 




xxxxxxxxxx
1


 
1
export const environment = {
2
  production: false,
3
  apiUrl: 'https://bootiful-angular.herokuapp.com'
4
};


Update auth-interceptor.ts to use environment.apiUrl.

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

TypeScript
 




xxxxxxxxxx
1
12


 
1
import { environment } from '../../../environments/environment';
2
 
          
3
@Injectable()
4
export class AuthInterceptor implements HttpInterceptor {
5
 
          
6
  ...
7
 
          
8
  private async handleAccess(request: HttpRequest<any>, next: HttpHandler): Promise<HttpEvent<any>> {
9
    const allowedOrigins = [environment.apiUrl];
10
    ...
11
  }
12
}


Update notes.service.ts as well.

notes/src/app/note/note.service.ts

TypeScript
 




xxxxxxxxxx
1
15


 
1
import { environment } from '../../environments/environment';
2
...
3
 
          
4
export class NoteService {
5
  ...
6
  api = `${environment.apiUrl}/api/notes`;
7
  ...
8
 
          
9
  find(filter: NoteFilter): Observable<Note[]> {
10
    ...
11
 
          
12
    const userNotes = `${environment.apiUrl}/user/notes`;
13
    ...
14
  }
15
}


Use PostgreSQL for the Production Database

H2 is a SQL database that works nicely for development. In production, you’re going to want something a little more robust. Personally, I like PostgreSQL so I’ll use it in this example.

Similar to Angular’s environments, Spring and Maven have profiles that allow you to enable different behavior for different environments.

Open notes-api/build.gradle.kts and change the H2 dependency so PostgreSQL is used when -Pprod is passed in.

Kotlin
 




xxxxxxxxxx
1


 
1
if (project.hasProperty("prod")) {
2
    runtimeOnly("org.postgresql:postgresql")
3
} else {
4
    runtimeOnly("com.h2database:h2")
5
}


At the bottom of the file, add the following code to make the prod profile the default when -Pprod is included in Gradle commands.

Kotlin
 




xxxxxxxxxx
1


 
1
val profile = if (project.hasProperty("prod")) "prod" else "dev"
2
 
          
3
tasks.bootRun {
4
    args("--spring.profiles.active=${profile}")
5
}
6
 
          
7
tasks.processResources {
8
    rename("application-${profile}.properties", "application.properties")
9
}


Rename notes-api/src/main/resources/application.properties to application-dev.properties and add a URL for H2 so it will persist to disk, which retains data through restarts.

Properties files
 




xxxxxxxxxx
1


 
1
allowed.origins=http://localhost:4200
2
spring.datasource.url=jdbc:h2:file:./build/h2db/notes;DB_CLOSE_DELAY=-1


Create a notes-api/src/main/docker/postgresql.yml so you can test your prod profile settings.

YAML
 




xxxxxxxxxx
1


 
1
version: '2'
2
services:
3
  notes-postgresql:
4
    image: postgres:12.1
5
    environment:
6
      - POSTGRES_USER=notes
7
      - POSTGRES_PASSWORD=
8
    ports:
9
      - 5432:5432


Create an application-prod.properties file in the same directory as application-dev.properties. You’ll override these properties with environment variables when you deploy to Heroku.

notes-api/src/main/resources/application-prod.properties

Properties files
 




xxxxxxxxxx
1


1
allowed.origins=http://localhost:4200
2
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
3
spring.jpa.hibernate.ddl-auto=update
4
spring.datasource.url=jdbc:postgresql://localhost:5432/notes
5
spring.datasource.username=notes
6
spring.datasource.password=


The word user is a keyword in PostgreSQL, so you’ll need to change user to username in the Note entity.

notes-api/src/main/kotlin/com/okta/developer/notes/DemoApplication.kt

Kotlin
 




xxxxxxxxxx
1


 
1
data class Note(@Id @GeneratedValue var id: Long? = null,
2
                var title: String? = null,
3
                var text: String? = null,
4
                @JsonIgnore var username: String? = null)


This will cause compilation errors and you’ll need to rename method names and variables to fix them.

Click to see the diff

You won’t want to pre-populate your production database with a bunch of notes, so add a @Profile annotation to the top of DataInitializer so it only runs for the dev profile.

Kotlin
 




xxxxxxxxxx
1


 
1
import org.springframework.context.annotation.Profile
2
...
3
 
          
4
@Profile("dev")
5
class DataInitializer(val repository: NotesRepository) : ApplicationRunner {...}


To test your profiles, start PostgreSQL using Docker Compose.

Shell
 




xxxxxxxxxx
1


1
docker-compose -f src/main/docker/postgresql.yml up


If you have PostreSQL installed and running locally, you’ll need to stop the process for Docker Compose to work.

In another terminal, run your Spring Boot app.

Shell
 




xxxxxxxxxx
1


 
1
source okta.env
2
./gradlew bootRun -Pprod


If it starts OK, confirm your Angular app can talk to it and get ready to deploy to production!

Shell
 




xxxxxxxxxx
1


 
1
git commit -am "Configure environments for production"


Deploy Spring Boot to Heroku

One of the easiest ways to interact with Heroku is with the Heroku CLI. Install it before proceeding with the instructions below.

Shell
 




xxxxxxxxxx
1


 
1
brew tap heroku/brew && brew install heroku


Open a terminal and log in to your Heroku account.

Shell
 




xxxxxxxxxx
1


 
1
heroku login


Heroku expects you to have one Git repo per application. However, in this particular example, there are multiple apps in the same repo. This is called a "monorepo", where many projects are stored in the same repository.

Luckily, there’s a heroku-buildpack-monorepo that allows you to deploy multiple apps from the same repo.

You should already have a Heroku app that you added Okta to. Let’s use it for hosting Spring Boot. Run heroku apps and you’ll see the one you created.

Shell
 




xxxxxxxxxx
1


 
1
$ heroku apps
2
=== matt.raible@okta.com Apps
3
bootiful-angular


You can run heroku config -a $APP_NAME to see your Okta variables. In my case, I’ll be using bootiful-angular for $APP_NAME.

Associate your existing Git repo with the app on Heroku.

Shell
 




xxxxxxxxxx
1


1
heroku git:remote -a $APP_NAME


Set the APP_BASE config variable to point to the notes-api directory. While you’re there, add the monorepo and Gradle buildpacks.

Shell
 




xxxxxxxxxx
1


1
heroku config:set APP_BASE=notes-api
2
heroku buildpacks:add https://github.com/lstoll/heroku-buildpack-monorepo
3
heroku buildpacks:add heroku/gradle


Attach a PostgreSQL database to your app.

Shell
 




xxxxxxxxxx
1


 
1
heroku addons:create heroku-postgresql


As part of this process, Heroku will create a DATASOURCE_URL configuration variable. It will also automatically detect Spring Boot and set variables for SPRING_DATASOURCE_URL, SPRING_DATASOURCE_USERNAME, AND SPRING_DATASOURCE_PASSWORD. These values will override what you have in application-prod.properties.

By default, Heroku’s Gradle support runs ./gradlew build -x test. Since you want it to run ./gradlew bootJar -Pprod, you’ll need to override it by setting a GRADLE_TASK config var.

Shell
 




xxxxxxxxxx
1


 
1
heroku config:set GRADLE_TASK="bootJar -Pprod"


The $OKTA_* environment variables don’t have the same names as the Okta Spring Boot starter expects. This is because the Okta Heroku Add-On creates two apps: SPA and web. The web app’s config variables end in _WEB. You’ll have to make some changes so those variables are used for the Okta Spring Boot starter. One way to do so is to create a Procfile in the notes-api directory.

Shell
 




xxxxxxxxxx
1


1
web: java -Dserver.port=$PORT -Dokta.oauth2.client-id=${OKTA_OAUTH2_CLIENT_ID_WEB} -Dokta.oauth2.client-secret=${OKTA_OAUTH2_CLIENT_SECRET_WEB} -jar build/lib/*.jar


I think it’s easier to rename the variable, so that’s what I recommend. Run the following command and remove _WEB from the two variables that have it.

Shell
 




xxxxxxxxxx
1


 
1
heroku config:edit


Now you’re ready to deploy! Take a deep breath and witness how Heroku can deploy your Spring Boot + Kotlin app with a simple git push.

Shell
 




xxxxxxxxxx
1


1
git push heroku master


When I ran this command, I received this output:

Shell
 




xxxxxxxxxx
1
34


 
1
remote: Compressing source files... done.
2
remote: Building source:
3
remote:
4
remote: -----> Monorepo app detected
5
remote:       Copied notes-api to root of app successfully
6
remote: -----> Gradle app detected
7
remote: -----> Spring Boot detected
8
remote: -----> Installing JDK 1.8... done
9
remote: -----> Building Gradle app...
10
remote: -----> executing ./gradlew bootJar -Pprod
11
remote:        Downloading https://services.gradle.org/distributions/gradle-6.0.1-bin.zip
12
remote:        ..........................................................................................
13
remote:        > Task :compileKotlin
14
remote:        > Task :compileJava NO-SOURCE
15
remote:        > Task :processResources
16
remote:        > Task :classes
17
remote:        > Task :bootJar
18
remote:
19
remote:        BUILD SUCCESSFUL in 1m 28s
20
remote:        3 actionable tasks: 3 executed
21
remote: -----> Discovering process types
22
remote:        Procfile declares types     -> (none)
23
remote:        Default types for buildpack -> web
24
remote:
25
remote: -----> Compressing...
26
remote:        Done: 91.4M
27
remote: -----> Launching...
28
remote:        Released v1
29
remote:        https://bootiful-angular.herokuapp.com/ deployed to Heroku
30
remote:
31
remote: Verifying deploy... done.
32
To https://git.heroku.com/bootiful-angular.git
33
   a1b10c4..6e298cf  master -> master
34
Execution time: 2 min. 7 s.


Run heroku open to open your app. You’ll be redirected to Okta to authenticate, then back to your app. It will display a 404 error message because you have nothing mapped to /. You can fix that by adding a HomeController with the following code.

Kotlin
 




xxxxxxxxxx
1
15


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
 
          
8
@RestController
9
class HomeController {
10
 
          
11
    @GetMapping("/")
12
    fun hello(@AuthenticationPrincipal user: OidcUser): String {
13
        return "Hello, ${user.fullName}"
14
    }
15
}


Commit this change and deploy it to Heroku.

Shell
 




xxxxxxxxxx
1


 
1
git commit -am "Add HomeController"
2
git push heroku master


Now when you access the app, it should say hello.

hello super admin

Deploy Angular to Heroku

An Angular app is composed of JavaScript, CSS, and HTML when built for production. It’s extremely portable because it’s just a set of static files. If you run ng build --prod, the production-ready files will be created in dist/<app-name>. In this section, you’ll learn how you can use your package.json scripts to hook into Heroku’s lifecycle and how to deploy them with a simple git push.

You’ll need to create another app on Heroku for the Angular frontend.

Shell
 




xxxxxxxxxx
1


 
1
heroku create


Set the APP_BASE config variable and add the necessary buildpacks to the app that was just created.

Shell
 




xxxxxxxxxx
1


1
APP_NAME=<app-name-from-heroku-create>
2
heroku config:set APP_BASE=notes -a $APP_NAME
3
heroku buildpacks:add https://github.com/lstoll/heroku-buildpack-monorepo -a $APP_NAME
4
heroku buildpacks:add heroku/nodejs -a $APP_NAME


Change notes/package.json to have a different start script.

JSON
 




xxxxxxxxxx
1


 
1
"start": "http-server-spa dist/notes index.html $PORT",


Add a heroku-postbuild script to your package.json:

JSON
 




xxxxxxxxxx
1


 
1
"heroku-postbuild": "ng build --prod && npm install -g http-server-spa"


Commit your changes, add a new Git remote for this app, and deploy!

Shell
 




xxxxxxxxxx
1


 
1
git commit -am "Prepare for Heroku"
2
git remote add angular https://git.heroku.com/$APP_NAME.git
3
git push angular master


When it finishes deploying, you can open your Angular app with:

Shell
 




xxxxxxxxxx
1


1
heroku open --remote angular


If you experience any issues, you can run heroku logs --remote angular to see your app’s log files.

You won’t be able to log in to your app until you modify its Login redirect URI on Okta. Log in to your Okta dashboard (tip: you can do this from the first Heroku app you created, under the Resources tab). Go to Applications > SPA > General > Edit. Add https://<angular-app-on-heroku>.herokuapp.com/callback to the Login redirect URIs and https://<angular-app-on-heroku>.herokuapp.com to the Logout redirect URIs.

You should be able to log in now, but you won’t be able to add any notes. This is because you need to update the allowed origins in your Spring Boot app. Run the following command to add an ALLOWED_ORIGINS variable in your Spring Boot app.

Shell
 




xxxxxxxxxx
1


1
heroku config:set ALLOWED_ORIGINS=https://$APP_NAME.herokuapp.com --remote heroku


Now you should be able to add a note. Pat yourself on the back for a job well done!

One issue you’ll experience is that you’re going to lose your data between restarts. This is because Hibernate is configured to update your database schema each time. Change it to simply validate your schema by overriding the ddl-auto value in application-prod.properties.

Shell
 




xxxxxxxxxx
1


 
1
heroku config:set SPRING_JPA_HIBERNATE_DDL_AUTO=validate --remote heroku


Make Your Angular App More Secure on Heroku

You’ve deployed your app to Heroku, but there are still a couple of security issues. The first is that if you access it using http (instead of https), it won’t work. You’ll get a blank page and an error from the Okta Angular SDK in your browser’s console.

The second issue is that you’ll score an F when you test it using securityheaders.com. Heroku has a blog post on using HTTP headers to secure your site that will help you improve your score.

Create a notes/static.json file with the configuration for secure headers and redirect all HTTP requests to HTTPS.

notes/static.json

JSON
 




xxxxxxxxxx
1
18


1
{
2
  "headers": {
3
    "/**": {
4
      "Content-Security-Policy": "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; frame-ancestors 'none'; connect-src 'self' https://*.okta.com https://*.herokuapp.com",
5
      "Referrer-Policy": "no-referrer, strict-origin-when-cross-origin",
6
      "Strict-Transport-Security": "max-age=63072000; includeSubDomains",
7
      "X-Content-Type-Options": "nosniff",
8
      "X-Frame-Options": "DENY",
9
      "X-XSS-Protection": "1; mode=block",
10
      "Feature-Policy": "accelerometer 'none'; camera 'none'; microphone 'none'"
11
    }
12
  },
13
  "https_only": true,
14
  "root": "dist/notes/",
15
  "routes": {
16
    "/**": "index.html"
17
  }
18
}


For static.json to be read, you have to use the Heroku static buildpack. This buildpack is made for SPA applications, so you can revert the scripts section of your package.json back to what you had previously. Add --prod to the build script since the static buildback uses this command.

notes/package.json

JSON
 




xxxxxxxxxx
1


1
"scripts": {
2
  "ng": "ng",
3
  "start": "ng serve",
4
  "build": "ng build --prod",
5
  "test": "ng test",
6
  "lint": "ng lint",
7
  "e2e": "ng e2e"
8
},


Commit your changes to Git, add the static buildpack, and redeploy your Angular app.

Shell
 




xxxxxxxxxx
1


 
1
git add .
2
git commit -am "Configure secure headers and static buildpack"
3
heroku buildpacks:add https://github.com/heroku/heroku-buildpack-static.git --remote angular
4
git push angular master


Now you’ll have a security report you can be proud of! 

Security results

Angular Deployment with ng deploy

In Angular CLI v8.3.0, an ng deploy command was introduced that allows you to automate deploying to many different cloud providers. I thought it’d be fun to try a few of these out and see if it’s possible to optimize the headers to get the same A rating that you were able to get with Heroku.

Below are the current providers and packages supported by this command.

Hosting provider npm package

Azure

@azure/ng-deploy

AWS S3

@jefiozie/ngx-aws-deploy

Firebase

@angular/fire

GitHub pages

angular-cli-ghpages

Netlify

@netlify-builder/deploy

Now

@zeit/ng-deploy

NPM

ngx-deploy-npm

In the following section, I’ll show you how to deploy to a few that piqued my interest (Firebase, Netlify, and AWS S3).

Angular Deployment to Firebase

Create a firebase branch so you can make changes without affecting the work you’ve done for Heroku deployments.

Shell
 




xxxxxxxxxx
1


1
git checkout -b firebase


Open a browser and go to firebase.google.com. Log in to your account, go to the console, and create a new project.

Run ng add @angular/fire in the notes directory and your new project should show up in the list. Select it to continue.

Shell
 




xxxxxxxxxx
1


1
? Please select a project: ng-notes-1337 (ng-notes-1337)
2
CREATE firebase.json (300 bytes)
3
CREATE .firebaserc (133 bytes)
4
UPDATE angular.json (3755 bytes)


Now you can run ng deploy and everything should work.

You’ll need to add the project’s URL as an allowed origin in your Spring Boot app on Heroku. Copy the printed Hosting URL value and run the following command.

Shell
 




xxxxxxxxxx
1


1
heroku config:edit --remote heroku


Add the new URL after your existing Heroku one, separating them with a comma. For example:

Shell
 




xxxxxxxxxx
1


 
1
ALLOWED_ORIGINS='https://stark-lake-39546.herokuapp.com,https://ng-notes-1337.web.app'


You’ll also need to modify your Okta SPA app to add your Firebase URL as a Login redirect URI and Logout redirect URI. For mine, I added:

  • Login redirect URI: https://ng-notes-1337.web.app/callback

  • Logout redirect URI: https://ng-notes-1337.web.app

Open your Firebase URL in your browser, log in, and you should be able to see the note you added on Heroku.

Strong Security Headers on Firebase

If you test your new Firebase site on securityheaders.com, you’ll score a D. Luckily, you can configure headers in your firebase.json file. Edit this file and add a headers key like the following:

JSON
 




xxxxxxxxxx
1
29


 
1
"headers": [ {
2
  "source": "/**",
3
  "headers": [
4
    {
5
      "key": "Content-Security-Policy",
6
      "value": "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; frame-ancestors 'none'; connect-src 'self' https://*.okta.com https://*.herokuapp.com"
7
    },
8
    {
9
      "key": "Referrer-Policy",
10
      "value": "no-referrer, strict-origin-when-cross-origin"
11
    },
12
    {
13
      "key": "X-Content-Type-Options",
14
      "value": "nosniff"
15
    },
16
    {
17
      "key": "X-Frame-Options",
18
      "value": "DENY"
19
    },
20
    {
21
      "key": "X-XSS-Protection",
22
      "value": "1; mode=block"
23
    },
24
    {
25
      "key": "Feature-Policy",
26
      "value": "accelerometer 'none'; camera 'none'; microphone 'none'"
27
    }
28
  ]
29
} ]


You don’t need to include a Strict-Transport-Security header because Firebase includes it by default.

Run ng deploy and you should get an A now!

security report

Commit your changes to your firebase branch.

Shell
 




xxxxxxxxxx
1


 
1
git add .
2
git commit -am "Add Firebase deployment"


Angular Deployment to Netlify

Netlify is a hosting provider for static sites that I’ve enjoyed using in the past. They offer continuous integration, HTML forms, AWS Lambda functions, and CMS functionality. I wrote about Netlify in Build a Secure Blog with Gatsby, React, and Netlify.

Check out your master branch and create a new netlify one.

Shell
 




xxxxxxxxxx
1


 
1
git checkout master
2
git checkout -b netlify


Before running the command to add Netlify support, you’ll need to create a Netlify account. Once you’re signed in, create a new site. Netlify makes it easy to connect a site via Git, but since I want to demonstrate ng deploy, you’ll need to create a temporary directory with an index.html file in it. I put "Hello, World" in the HTML file, then dragged the directory into my browser window.

new site from git

Click on Site Settings to copy your API ID to a text editor. You’ll also need a personal access token. To create one, click on your avatar in the top right > User settings > Applications and click New access token. Copy the generated token to your text editor.

Run the command below to add Netlify deployment support.

Shell
 




xxxxxxxxxx
1


 
1
ng add @netlify-builder/deploy


Copy and paste your API ID and personal access token when prompted, then run ng deploy to deploy your site.

Update your Spring Boot app on Heroku to allow your Netlify app URL:

Shell
 




xxxxxxxxxx
1


 
1
heroku config:edit --remote heroku


Make sure to append the URL to your existing ones, separating them with a comma.

Shell
 




xxxxxxxxxx
1


 
1
ALLOWED_ORIGINS='https://stark-lake-39546.herokuapp.com,https://ng-notes-1337.web.app,https://relaxed-brown-0b81d8.netlify.app'


You’ll also need to update your Okta app to whitelist the URL as a login and logout redirect.

If you try to log in, you’ll get a Page Not Found error stemming from Okta trying to redirect back to your app. This happens because Netlify doesn’t know your app is a SPA that manages its own routes. To fix this, create a _redirects file in the notes/src directory with the following contents.

Shell
 




xxxxxxxxxx
1


1
/*    /index.html   200


You can learn more about configuring Netlify for SPAs in their documentation.

Then, modify angular.json to include this file in its assets.

JSON
 




xxxxxxxxxx
1


1
"assets": [
2
  "src/_redirects",
3
  "src/favicon.ico",
4
  "src/assets"
5
],


Run ng deploy again and you should be able to log in successfully.

Better Security Headers on Netlify

If you test your new Firebase site on securityheaders.com, you’ll score a D. Netlify allows you to add custom headers to improve your score.

Create a src/_headers file with the following contents.

Plain Text
 




xxxxxxxxxx
1


 
1
/*
2
  Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; frame-ancestors 'none'; connect-src 'self' https://*.okta.com https://*.herokuapp.com
3
  Referrer-Policy: no-referrer, strict-origin-when-cross-origin
4
  X-Content-Type-Options: nosniff
5
  X-Frame-Options: DENY
6
  X-XSS-Protection: 1; mode=block
7
  Feature-Policy: accelerometer 'none'; camera 'none'; microphone 'none'


Modify angular.json to include this file in its assets.

JSON
 




xxxxxxxxxx
1


 
1
"assets": [
2
  "src/_headers",
3
  "src/_redirects",
4
  "src/favicon.ico",
5
  "src/assets"
6
],


Run ng deploy and you should get an A now!

security report

Commit your changes to the netlify branch.

Shell
 




xxxxxxxxxx
1


 
1
git add .
2
git commit -am "Add Netlify deployment"


The Netlify schematic writes your access token to angular.json, which is a security risk (if you push these changes to your source control system).

JSON
 




xxxxxxxxxx
1


 
1
"deploy": {
2
  "builder": "@netlify-builder/deploy:deploy",
3
  "options": {
4
    "outputPath": "dist/notes",
5
    "netlifyToken": "04b966f772XXX...",
6
    "siteId": "1dda959c-XXXX..."
7
  }
8
}


I was notified of this issue by GitGuardian, which we use to monitor our repos. If you check-in this change, make sure to delete the access token on Netlify.

Angular Deployment to AWS S3

Amazon Simple Storage Service (Amazon S3) is an object storage service that is a popular option for hosting static sites.

Check out your master branch and create a new aws one.

Shell
 




xxxxxxxxxx
1


 
1
git checkout master
2
git checkout -b aws


Before running the command to add S3 deployment support, you’ll need a few things:

  • An S3 Bucket
  • An AWS Region Name
  • A Secret Access Key
  • An Access Key ID

You’ll also need to create an AWS account. After creating an account, go to the Amazon S3 console. Click Create Bucket and give it a name you’ll remember. Use the default region selected for you and click Create Bucket.

To create the secret access key, go to your security credentials page. Expand the Access keys section, and then Create New Access Key. Click Show Access Key and copy the values into a text editor.

If you have trouble creating a secret access key, see this blog post.

Add the @jefiozie/ngx-aws-deploy package to deploy to S3:

Shell
 




xxxxxxxxxx
1


 
1
ng add @jefiozie/ngx-aws-deploy


When prompted, enter your region, bucket name, access key, and access key ID. When prompted for the folder, leave it blank.

This process writes these raw values to your angular.json. For a more secure setup, use environment variables: NG_DEPLOY_AWS_ACCESS_KEY_ID, NG_DEPLOY_AWS_SECRET_ACCESS_KEY, NG_DEPLOY_AWS_BUCKET and NG_DEPLOY_AWS_REGION.

Run ng deploy to deploy your Angular app to your AWS S3 bucket.

Next, you need to configure S3 for static website hosting. Go to your bucket > Properties > Static website hosting. Take note of the endpoint URL at the top of the card, you’ll need this in a minute.

Type index.html for the index and error document and click Save.

Static website hosting

By default, Amazon S3 blocks public access to your buckets. Go to the Permissions tab. Click the Edit button, clear Block all public access, and click Save.

The last step you need to do to make it public is add a bucket policy. Go to Permissions > Bucket Policy and paste the following into the editor, replacing {your-bucket-name} with your bucket’s name.

JSON
 




xxxxxxxxxx
1
16


 
1
{
2
    "Version": "2012-10-17",
3
    "Statement": [
4
        {
5
            "Sid": "PublicReadGetObject",
6
            "Effect": "Allow",
7
            "Principal": "*",
8
            "Action": [
9
                "s3:GetObject"
10
            ],
11
            "Resource": [
12
                "arn:aws:s3:::{your-bucket-name}/*"
13
            ]
14
        }
15
    ]
16
}


Click Save to continue.

At this point, you could navigate to https://<bucket-name>.s3.amazonaws.com/index.html in your browser, and the application will load. However, there’s no HTTP to HTTPS redirect or resolution of index.html by default. You can use CloudFront to solve these issues.

Open the CloudFront console and choose Create Distribution. Under the Web section, click the Get Started button. Click in the Origin Domain Name field and select your S3 bucket.

Set the Viewer Protocol Policy to Redirect HTTP to HTTPS, allow all HTTP methods, and enter index.html as the Default Root Object. Scroll to the bottom and click Create Distribution. When the Status column changes from In Progress to Deployed, navigate to the domain name in your browser.

Once you’ve created your distribution, it can take 20 minutes for it to deploy.

After your distribution is deployed, update your Spring Boot app on Heroku to allow your CloudFront URL:

Shell
 




xxxxxxxxxx
1


1
heroku config:edit --remote heroku


Make sure to append the URL to your existing ones, separating them with a comma.

Shell
 




xxxxxxxxxx
1


 
1
ALLOWED_ORIGINS='https://stark-lake-39546.herokuapp.com,https://ng-notes-1337.web.app,https://relaxed-brown-0b81d8.netlify.app,https://d2kytj28ukuxfr.cloudfront.net'


Update your Okta SPA app to whitelist the URL as a redirect, too.

If you try to log in, you’ll get a 403 when redirecting back to the site. To fix this, edit your distribution > Error pages. Create two custom error responses for 404 and 403. Return a path of /index.html and a 200: OK response code.

Now you should be able to authenticate to your Angular app on AWS successfully!

Awesome Security Headers with AWS CloudFront + S3

If you test your new CloudFront + S3 site on securityheaders.com, you’ll get an F. To solve this, you can use Lambda@Edge to add security headers.

Go to the Lambda Console and select the US-East-1 N region from a drop-down list in the top right. Click Create Function to create a new function.

Choose Author from scratch and name it securityHeaders. Under Permissions, select Create a new role from AWS Policy templates. Name the role securityHeaders-role and select Basic Lambda@Edge permissions (for CloudFront trigger). Click Create function.

In the Function code section, set index.js to have the following JavaScript.

JSON
 




xxxxxxxxxx
1
18


 
1
exports.handler = (event, context, callback) => {
2
 
          
3
  // get response
4
  const response = event.Records[0].cf.response;
5
  const headers = response.headers;
6
 
          
7
  // set headers
8
  headers['content-security-policy'] = [{key: 'Content-Security-Policy', value: "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; frame-ancestors 'none'; connect-src 'self' https://*.okta.com https://*.herokuapp.com"}];
9
  headers['referrer-policy'] = [{key: 'Referrer-Policy', value: 'no-referrer, strict-origin-when-cross-origin'}];
10
  headers['strict-transport-security'] = [{key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubdomains'}];
11
  headers['x-content-type-options'] = [{key: 'X-Content-Type-Options', value: 'nosniff'}];
12
  headers['x-frame-options'] = [{key: 'X-Frame-Options', value: 'DENY'}];
13
  headers['x-xss-protection'] = [{key: 'X-XSS-Protection', value: '1; mode=block'}];
14
  headers['feature-policy'] = [{key: 'Feature-Policy', value: "accelerometer 'none'; camera 'none'; microphone 'none'"}];
15
 
          
16
  // return modified response
17
  callback(null, response);
18
};


Click Save and add a trigger. Select CloudFront and Deploy to Lambda@Edge. For the distribution, use the ID of your CloudFront distribution. Set the event to be Origin response. Select the "I acknowledge…" checkbox and click Deploy.

Go back to your CloudFront Console and wait for the deploy to complete.

Try your CloudFront domain again on securityheaders.com. You should get an A this time.

security report

Phew! AWS certainly doesn’t make things easy.

Commit your changes to the aws branch.

Shell
 




xxxxxxxxxx
1


1
git commit -am "Add AWS S3 deployment"


If you committed the changes in your angular.json file, you should delete your access key in My Account > Security Credentials. When I made this mistake, Amazon emailed me right away and made me delete my access key and change my password.

Learn More About Angular and Spring Boot

In this tutorial, you learned how to upgrade Angular and Spring Boot to their latest versions and make them production-ready. You used the Okta add-on for Heroku to add OAuth 2.0 + OIDC to both apps, then deployed them to Heroku. After you got them working on Heroku with PostgreSQL, you learned how to deploy the Angular app to Firebase, Netlify, and AWS.

You learned how to make your Angular app more secure with security headers, force HTTPS, and make each cloud provider SPA-aware. You can find the source code for the completed example in the @oktadeveloper/okta-angular-deployment-example repository.

This blog post is the third in a series. The first two posts are below.

  1. Build a CRUD App with Angular 9 and Spring Boot 2.2

  2. Build Beautiful Angular Apps with Bootstrap

I’ll be publishing a 4th and final blog post that shows you how to containerize and deploy your Angular + Spring Boot apps with Docker. This tutorial will also show how to combine them into a JAR and use server-side authorization code flow (the most secure OAuth 2.0 flow). As icing on the cake, I’ll show you how to deploy to Heroku, Knative on Google Cloud, and Cloud Foundry!

If you liked this tutorial, we have several others on Angular and Spring Boot.

If you have any questions, please leave a comment below. Follow us on Twitter, LinkedIn, or Facebook to be notified when we publish new tutorials. We have a popular YouTube channel too!

Changelog:


Topics:
angular, api, aws, firebase, heroku, netlify, spring boot

Published at DZone with permission of Matt Raible , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}