Build a Secure Notes Application with Kotlin, TypeScript, and Okta

0
925

I love my job as a developer advocate at Okta. I get to learn a lot, write interesting blog posts and create example apps with cool technologies like Kotlin, TypeScript, Spring Boot, and Angular, which I’m about to demo. When it comes to writing Hello World apps with authentication, I can whip one out in a few minutes. That isn’t because I’m a particularly good programmer, it’s because the languages, frameworks, tools, and platforms available to developers are impressive.

In this tutorial, I’ll show you how to write a note-taking application in Kotlin and TypeScript – two of the fastest growing languages of 2017. You’ll use two popular frameworks, Spring Boot and Angular, to make development super fast. Along the way, I’ll show you a few tips and tricks from my favorite development IDE, IntelliJ IDEA. Finally, we’ll leverage Angular CLI and start.spring.io to generate application skeletons.

As with any good example app, you’ll want to deploy it securely, so I’ll show you how to do that using Okta’s Identity APIs and our new Spring Boot starter. The Okta Spring Boot starter allows you to make your API into a resource server that can read and validate access tokens sent to it. The diagram below shows how a resource server fits into an OAuth architecture.

OAuth 2.0 Actors

Phew! That’s a lot of buzzwords for one article. Don’t worry, I’ve confirmed it’s possible to develop this app in even less time than it takes to deploy and secure it. And developing is fun, so let’s get started!

#Build a Notes API with Kotlin and Spring Boot

Start building the API for your application by navigating your favorite browser to start.spring.io. Select Kotlin as your language, and choose Web, H2, JPA, Rest Repositories, and DevTools. You’ll notice in the screenshot below that I changed the group and artifact names too. Please use these same names, so your package and class names match this tutorial.

start.spring.io

Click Generate Project and expand the zip file after downloading. If you don’t have IntelliJ IDEA installed, now’s a good time to try it out. It’s a great IDE for Java, Kotlin, Groovy, TypeScript, JavaScript, HTML, and Sass/CSS. One of its killer features is the ability to copy/paste Java code into a Kotlin class and have it auto-converted to Kotlin on-the-fly!

You can also turn on automatic-compilation-on-save and reap the benefits of Spring Boot’s DevTools that restart your app when files change.

  • Go to Preferences > Build, Execution, Deployment > Compiler and enable “Build project automatically”
  • Open the Action window:
    • Linux: CTRL+SHIFT+A
    • Mac: SHIFT+COMMAND+A
    • Windows: CTRL+ALT+SHIFT+/
  • Enter Registry… and enable compiler.automake.allow.when.app.running

Start by creating a new Note entity in src/main/kotlin/com/okta/developer/notes/NotesApplication.kt.

@SpringBootApplication
class NotesApplication

fun main(args: Array<String>) {
    SpringApplication.run(NotesApplication::class.java, *args)
}

@Entity
data class Note(@Id @GeneratedValue var id: Long? = null,
                var text: String? = null, var user: String? = null)

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.

@RepositoryRestResource
interface NotesRepository : JpaRepository<Note, Long>

The extends syntax differs from Java and is a lot more concise (a colon instead of extends).

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

@Component
class DataInitializer(val repository: NotesRepository) : ApplicationRunner {

    @Throws(Exception::class)
    override fun run(args: ApplicationArguments) {
        listOf("Note 1", "Note 2", "Note 3").forEach {
            repository.save(Note(text = it, user = "user"))
        }
        repository.findAll().forEach { println(it) }
    }
}

This example shows constructor injection, but Kotlin also supports field injection with @Autowired.

Start the app in your IDE using its Spring Boot tooling, or from the command line using mvnw spring-boot:run. If you’re on a Mac or Linux, you might need to use ./mvnw spring-boot:run.

You should see the following printed to your console on startup.

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

I recommend installing HTTPie, a command-line HTTP client that is much easier to use than curl. Use HTTPie to query the /notes endpoint provided by Spring Data REST’s @RepositoryRestResource.

http localhost:8080/notes

The result will look like the following screenshot.

http request screenshot

Create a HomeController (in the same NotesApplication.kt file) and use it to filter notes by the currently logged-in user.

import java.security.Principal

@RestController
class HomeController(val repository: NotesRepository) {

    @GetMapping("/")
    fun home(principal: Principal): List<Note> {
        println("Fetching notes for user: ${principal.name}")
        val notes = repository.findAllByUser(principal.name)
        if (notes.isEmpty()) {
            return listOf()
        } else {
            return notes
        }
    }
}

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.

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

If you try to access this new endpoint, you’ll get an error that the Principal parameter is not defined.

$ http localhost:8080
HTTP/1.1 500
Connection: close
Content-Type: application/json;charset=UTF-8
Date: Fri, 11 Aug 2017 07:36:46 GMT
Transfer-Encoding: chunked
{
    "error": "Internal Server Error",
    "exception": "java.lang.IllegalArgumentException",
    "message": "Parameter specified as non-null is null: method 
                com.okta.developer.notes.HomeController.home, parameter principal",
    "path": "/",
    "status": 500,
    "timestamp": 1502437006005
}

Spring MVC throws a 500 error because it has no knowledge of a logged-in user. Add the Spring Security starter to your pom.xml to enable security in your application.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Restart the Maven process to download this new dependency and add it to the classpath.

If you navigate to http://localhost:8080 in your browser, you will see a basic authentication dialog. The command line will yield similar results.

{
    "error": "Unauthorized",
    "message": "Full authentication is required to access this resource",
    "path": "/",
    "status": 401,
    "timestamp": 1502437281185
}

The Spring Security starter creates a default user with username “user” and a password that changes every time you start the application. You can find this password in your terminal, similar to the one below.

Using default security password: 103c55b4-2760-4830-9bca-a06a87d384f9

Change the user’s password so it’s the same every time by adding the following to src/main/resources/application.properties.

security.user.password=kotlin is fun!

After the change, verify that this HTTPie command works.

$ http --auth user:'kotlin is fun!' localhost:8080
HTTP/1.1 200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/json;charset=UTF-8
Date: Fri, 11 Aug 2017 07:47:10 GMT
Expires: 0
Pragma: no-cache
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
[
    {
        "id": 1,
        "text": "Note 1"
    },
    {
        "id": 2,
        "text": "Note 2"
    },
    {
        "id": 3,
        "text": "Note 3"
    }
]

The reason you don’t see the user property in the JSON above is because I added a @JsonIgnore annotation to the Note class.

import com.fasterxml.jackson.annotation.JsonIgnore

@Entity
data class Note(@Id @GeneratedValue var id: Long? = null,
                var text: String? = null,
                @JsonIgnore var user: String? = null)

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

@Component
@RepositoryEventHandler(Note::class)
class AddUserToNote {

    @HandleBeforeCreate
    fun handleCreate(note: Note) {
        val username: String =  SecurityContextHolder.getContext().getAuthentication().name
        println("Creating note: $note with user: $username")
        note.user = username
    }
}

After adding the handler, saving your files, and waiting for your API to restart, you’ll be able to run the following commands with wild success.

http --auth user:'kotlin is fun!' POST localhost:8080/notes text='Note 4'
http --auth user:'kotlin is fun!' PUT localhost:8080/notes/4 text='Remember the Milk!'
http --auth user:'kotlin is fun!' DELETE localhost:8080/notes/4

Your API works and is locked down, but you still only have one user. Rather than spending time setting up database tables and encrypting passwords, you can use Okta’s APIs to manage, authenticate, and authorize your users securely. To get started with Okta, sign up for a free-forever developer account.

#The Okta Spring Boot Starter

Okta provides a Spring Boot starter that integrates with Spring Security and its OAuth 2.0 support. Replace the Spring Security starter with the Okta Spring Security starter.

<dependency>
      <groupId>com.okta.spring</groupId>
      <artifactId>okta-spring-security-starter</artifactId>
      <version>0.1.0</version>
</dependency>    

You’ll also need to upgrade the OAuth library used by Spring Security to the latest version.

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.2.0.RELEASE</version>
        </dependency>
    </dependencies>
</dependencyManagement>

After modifying your pom.xml, configure it with your Okta settings.

Add an Authorization Server

Log in to your Okta account and click the Admin button in the top right. Navigate to Security > API in the top menu and click on the Add Authorization Server button. Use a name and audience that works for you. For example, I chose:

  • Name: My AS
  • Audience: people-of-earth

My AS

Copy and paste the Issuer URL and audience values into application.properties:

okta.oauth.issuer={issuer}
okta.oauth.audience={audience}

The Okta Spring Security starter expects you to have a custom claim called groups. Define a custom claim with these values:

  • Name: groups
  • Value Type: Groups
  • Filter: Regex – .*

You’ll also need to add a new Access Policy and Rule that gives everyone access. Of course, you can lock these down if you want to, but it’s best to use the defaults for this tutorial.

Finally, navigate to Security > API > Trusted Origins and add http://localhost:4200 as an Origin URL with CORS support.

Add an OpenID Connect Application

Navigate to Applications and click on Add Applications > Create New App. Select Single Page App (SPA) using OpenID Connect and click Create. Give the application a name (e.g. “My OIDC App”) and specify http://localhost:4200 as a Login redirect URI. Your upcoming Angular client will use this value. Click Save and admire your handiwork!

My OIDC App

Click on the Assignments tab and click Assign. Assign the “Everyone” group and click Done.

Copy the Client ID value into application.properties.

okta.oauth.clientId={client-id}

Before you start building the Angular client, add a CORS filter to NotesApplication so cross-origin requests can succeed.

class NotesApplication {

    @Bean
    open fun simpleCorsFilter(): FilterRegistrationBean {
        val source = UrlBasedCorsConfigurationSource()
        val config = CorsConfiguration()
        config.allowCredentials = true
        config.allowedOrigins = listOf("http://localhost:4200")
        config.allowedMethods = listOf("*");
        config.allowedHeaders = listOf("*")
        source.registerCorsConfiguration("/**", config)
        val bean = FilterRegistrationBean(CorsFilter(source))
        bean.order = Ordered.HIGHEST_PRECEDENCE
        return bean
    }
}

fun main(args: Array<String>) {
    SpringApplication.run(NotesApplication::class.java, *args)
}
…

You can see the final version of this file on GitHub.

I hope you’ve enjoyed this quick tour of Kotlin and saw how its concise syntax can be a lot of fun. In May 2017, Kotlin was announced as an officially supported language on Android, giving the language quite a bit of attention. You can learn more about Kotlin on kotlinlang.org.

#Build an Angular UI with TypeScript and Angular CLI

Angular CLI is a convenient way to create Angular applications. It generates a project skeleton, installs all the dependencies, and configures Webpack to compile TypeScript and optimize for production.

Install Angular CLI using Facebook’s Yarn.

yarn add global @angular/cli

Or using npm (npm install -g @angular/cli).

Then create a new project using its ng command.

ng new client

It takes a minute or two to install all the dependencies. After it finishes, you can run ng serve to view the app, or ng test to run unit tests. If you want to verify that the end-to-end tests pass, run ng e2e.

Create a service and component using the generate (alias: g) command. You can use s as an alias for service and c as an alias for component.

ng g service note
ng g component note-list
ng g c note-detail
ng g c login

The service files are generated in client/src/app by default, but I like to move them into a shared/{service} directory.

mkdir -p src/app/shared/note
mv src/app/note.service.* src/app/shared/note

At this point, I’d recommend opening your Angular client in IntelliJ IDEA. It has excellent TypeScript support and will auto-import classes for you, just like it does for Java and Kotlin.

Add the NoteService to the providers list in client/src/app/app.module.ts. Notice that Angular CLI has already added the generated components to the declarations list.

@NgModule({
  declarations: [
    AppComponent,
    NoteListComponent,
    NoteDetailComponent,
    LoginComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [NoteService],
  bootstrap: [AppComponent]
})
export class AppModule {
}

Modify client/src/app/shared/note/note.service.ts to have a getAll() method that talks to the API.

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class NoteService {
  public API = 'http://localhost:8080';
  public NOTE_API = this.API + '/notes';

  constructor(private http: HttpClient) {
  }

  getAll(): Observable<any> {
    return this.http.get(this.API);
  }
}

TIP: If you’re using IntelliJ IDEA, I recommend you install the Angular 2 TypeScript Live Templates. They drastically reduce the amount of code you have to write with several code-generation shortcuts.

In client/src/app/notes-list/note-list.component.ts, add a dependency on NoteService and get all the user’s notes when the component loads.

import { NoteService } from '../shared/note/note.service';

export class NoteListComponent implements OnInit {
  notes: Array<any>;

  constructor(private noteService: NoteService) {
  }

  ngOnInit() {
    this.noteService.getAll().subscribe(data => {
      this.notes = data;
    }, error => console.error(error));
  }
}

Replace the HTML in client/src/app/note-list/note-list.component.html with a few lines to render the notes list.

<h2>Notes List</h2>
<div *ngFor="let note of notes">
  {{note.text}}
</div>

If you try to make things work at this point, you won’t be able to access your API because it expects you to send an access token in an Authorization header.

Install the Okta Sign-In Widget to authenticate using the “My OIDC” app you already created and get an access token.

yarn add @okta/okta-signin-widget

Create an OktaAuthService that can be used to render the Sign-In Widget and handle authentication. The following TypeScript code should be in client/src/app/shared/okta/okta.service.ts. Be sure to replace {dev-id}{client-id}, and {as-server-url} with values appropriate for your Okta application and authorization server.

import { Injectable } from '@angular/core';
import * as OktaSignIn from '@okta/okta-signin-widget/dist/js/okta-sign-in.min.js'
import { ReplaySubject } from 'rxjs/ReplaySubject';
import { Observable } from 'rxjs/Observable';
import { Router } from '@angular/router';

@Injectable()
export class OktaAuthService {

  signIn = new OktaSignIn({
    baseUrl: 'https://dev-{dev-id}.oktapreview.com',
    clientId: '{client-id}',
    redirectUri: 'http://localhost:4200',
    authParams: {
      issuer: '{as-server-url}',
      responseType: ['id_token', 'token'],
      scopes: ['openid', 'email', 'profile']
    }
  });

  private userSource: ReplaySubject<any>;
  public user$: Observable<any>;

  constructor(private router: Router) {
    this.userSource = new ReplaySubject<any>(1);
    this.user$ = this.userSource.asObservable();
  }

  isAuthenticated() {
    // Checks if there is a current accessToken in the TokenManger.
    return !!this.signIn.tokenManager.get('accessToken');
  }

  login(next?: string) {
    if (next) {
      this.router.navigate(['login', {next: next}]);
    } else {
      this.router.navigate(['login']);
    }
  }

  showLogin() {
    // Launches the widget and stores the tokens
    try {
      this.signIn.renderEl({el: '#okta-signin-container'}, response => {
        if (response.status === 'SUCCESS') {
          response.forEach(token => {
            if (token.idToken) {
              this.signIn.tokenManager.add('idToken', token);
            }
            if (token.accessToken) {
              this.signIn.tokenManager.add('accessToken', token);
            }
          });
          this.userSource.next(this.idTokenAsUser);
          this.signIn.hide();
        } else {
          console.error(response);
        }
      });
    } catch (exception)  {
      // An instance of the widget has already been rendered. Call remove() first.
    }
  }

  get idTokenAsUser() {
    const token = this.signIn.tokenManager.get('idToken');
    return {
      name: token.claims.name,
      email: token.claims.email,
      username: token.claims.preferred_username
    };
  }

  async logout() {
    // Terminates the session with Okta and removes current tokens.
    this.signIn.tokenManager.clear();
    await this.signIn.signOut();
    this.signIn.remove();
    this.userSource.next(undefined);
    this.login();
  }
}

NOTE: I realize this is quite a bit of code to render a sign-in form. The good news is we’ll be wrapping much of it into an okta-angular library soon.

Create an OktaAuthGuard in client/src/app/shared/okta/okta.guard.ts. You’ll use this to guard routes so they can’t be activated if the user isn’t authenticated.

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { OktaAuthService } from './okta.service';

@Injectable()
export class OktaAuthGuard implements CanActivate {
  signIn;
  authenticated;

  constructor(private oktaService: OktaAuthService) {
    this.signIn = oktaService;
  }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    this.authenticated = this.oktaService.isAuthenticated();
    if (this.authenticated) {
      return true;
    }
    this.signIn.login();
    return false;
  }
}

Create an OktaAuthInterceptor in client/src/app/shared/okta.interceptor.ts to automatically add an Authorization header to HTTP requests.

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor, HttpErrorResponse, HttpResponse
} from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { OktaAuthService } from './okta.service';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';

@Injectable()
export class OktaAuthInterceptor implements HttpInterceptor {

  constructor(private oktaService: OktaAuthService) {
  }

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

    if (this.oktaService.isAuthenticated()) {
      const accessToken = this.oktaService.signIn.tokenManager.get('accessToken');
      request = request.clone({
        setHeaders: {
          Authorization: `${accessToken.tokenType} ${accessToken.accessToken}`
        }
      });
    }

    return next.handle(request).map((event: HttpEvent<any>) => {
      if (event instanceof HttpResponse) {
        return event;
      }
    }).catch(error => {
      if (error instanceof HttpErrorResponse) {
        if (error.status === 401) {
          this.oktaService.login();
          return Observable.create(error);
        }
      }
    });
  }
}

In client/src/app/app.module.ts, define the routes for the application, with canActivate guards for the note-related routes.

const appRoutes: Routes = [
  {path: 'login', component: LoginComponent},
  {path: 'notes', component: NoteListComponent, canActivate: [OktaAuthGuard]},
  {path: 'notes/:id', component: NoteDetailComponent, canActivate: [OktaAuthGuard]},
  {path: '', redirectTo: '/notes', pathMatch: 'full'}
];

Import HttpClientModule and RouterModule, configure OktaAuthService and OktaAuthGard as providers, and define OktaAuthInterceptor as an HTTP interceptor.

@NgModule({
  declarations: [
    AppComponent,
    NoteListComponent,
    NoteDetailComponent,
    LoginComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    RouterModule.forRoot(appRoutes)
  ],
  providers: [NoteService, OktaAuthService, OktaAuthGuard, {
    provide: HTTP_INTERCEPTORS,
    useClass: OktaAuthInterceptor,
    multi: true
  }],
  bootstrap: [AppComponent]
})
export class AppModule {
}

Modify LoginComponent (in client/src/app/login/login.component.ts) to show the homepage if the user is logged-in, or the sign-in widget if not.

export class LoginComponent implements OnInit {

  constructor(private oktaService: OktaAuthService,
              private router: Router) {
  }

  ngOnInit() {
    if (this.oktaService.isAuthenticated()) {
      this.router.navigate(['/']);
    } else {
      this.oktaService.showLogin();
    }

    // user authentication listener
    this.oktaService.user$.subscribe(user => {
      this.router.navigate(['/']);
    });
  }
}

In the same directory, update login.component.html to have a div for the sign-in widget to render in.

<div id="okta-signin-container"></div>

Modify client/src/app/app.component.html to show the user’s name and add a <router-outlet> for rendering all the routes.

<h1>{{title}}</h1>

<div *ngIf="user">
  Welcome {{user?.name}}!
  <button (click)="oktaService.logout()">Logout</button>
</div>

<router-outlet [hidden]="!user"></router-outlet>

Then update client/src/app/app.component.ts so it has a reference to userand oktaService. Notice that this class populates the user variable if the user is already authenticated (for example, they refreshed their browser) or if they sign in with the widget. The this.oktaService.user$ is an Observable that can be subscribed to for changes in the user.

export class AppComponent implements OnInit {
  title = 'My Notes';
  user;

  constructor(public oktaService: OktaAuthService) {
  }

  ngOnInit() {
    if (this.oktaService.isAuthenticated()) {
      this.user = this.oktaService.idTokenAsUser;
    }

    this.oktaService.user$.subscribe(user => {
      this.user = user;
    });
  }
}

To make the Okta Sign-In Widget look good, add its default CSS files to client/src/styles.

@import '~https://ok1static.oktacdn.com/assets/js/sdk/okta-signin-widget/2.1.0/css/okta-sign-in.min.css';
@import '~https://ok1static.oktacdn.com/assets/js/sdk/okta-signin-widget/2.1.0/css/okta-theme.css';

After making all these changes, you should be able to fire up http://localhost:4200 (using ng serve) and see a sign in form.

Sign-In Widget

After signing in, you should see the notes list, but no records in it.

Empty Notes List

To make sure I could add, edit, and delete notes, I wrote a bunch of TypeScript and HTML. I also added Angular Material using yarn add @angular/material @angular/cdk.

You can see the results in the GitHub repository for this article. In particular, the code in the following files:

The final client/src/app/app.module.ts shows all the imports needed for Angular Material. Its stylesheets are referenced in client/src/styles.css. If you copy the code from these files into your project, you’ll have a working notes app with authentication!

The screenshots below show the fruits of my labor.

Material Notes List

Add Note

Edit Note

NOTE: There’s one issue with Okta’s Sign-In Widget I still haven’t fully figured out. Not every time, but everyone once it in a while, it requires me to move my mouse or click on the screen to make the notes list load after logging in. I opened an issue for this and tried the suggested solution, but it doesn’t work 100% of the time.

You now know how to build an Angular client with TypeScript, using Okta’s Sign-In Widget for authentication.

If you’re ambitious, you could even turn the client into a progressive web app (PWA), enabling offline access and faster load times. There are a couple of posts about developing PWAs on the Okta Developer Blog if you’re interested in learning more.

I also published The Ultimate Guide to Progressive Web Applications on scotch.io earlier this summer.

My good buddy Josh Long and I recently hosted a live-coding session where we developed a Spring Boot microservices architecture on the backend and an Angular PWA on the front-end. The code we wrote is very similar to the code in this article. You can check the video out for reference on YouTube.

#Deploy to Production

It’s cool to see an application running locally, but it’s even better to see it up and running in production.

My platform of choice for deployment is Cloud Foundry. To get started, you’ll need to create an account and install the command line tools.

brew tap cloudfoundry/tap && brew install cf-cli
cf login -a api.run.pivotal.io

Before deploying, you’ll need to create a couple of files to build the application artifacts and tell Cloud Foundry where everything lives. Create a manifest.yml file in the root directory and specify where the files to upload are. Note that this file expects your apps to be in the same directory, with Spring Boot in a server subdirectory and the Angular app in a client subdirectory.

---
applications:

- name: notes-server 
  host: notes-by-kotlin
  path: ./server/target/notes-0.0.1-SNAPSHOT.jar
  env :
    FORCE_HTTPS: true

- name: notes-client
  host: notes-with-typescript
  path: ./client/dist/
  env :
    FORCE_HTTPS: true

Then, create a build.sh script that packages the server and client, and replaces the development URLs with the production URLs.

#!/bin/bash

start=`pwd`

# set origin for client on server
sed -i -e "s|http://localhost:4200|https://notes-with-typescript.cfapps.io|g" $start/server/src/main/kotlin/com/okta/developer/notes/NotesApplication.kt

mvn clean package -f $start/server/pom.xml

cd $start/client
rm -rf dist
# set API URL
sed -i -e "s|http://localhost:8080|https://notes-by-kotlin.cfapps.io|g" $start/client/src/app/shared/note/note.service.ts
# set redirectURI to client URI
sed -i -e "s|http://localhost:4200|https://notes-with-typescript.cfapps.io|g" $start/client/src/app/shared/okta/okta.service.ts
yarn && ng build -prod --aot
touch dist/Staticfile

cd $start
cf push

# reset and remove changed files
git checkout $start
rm -rf $start/server/src/main/kotlin/com/okta/developer/notes/NotesApplication.kt-e
rm -rf $start/client/src/app/shared/note/note.service.ts-e

After logging into Cloud Foundry, you can run the build script (using sh build.sh) and deploy everything. If you receive an error about the host name being in use, try a different host name in manifest.yml.

Run cf apps to see the URLs of the applications you deployed.

name           requested state   instances   memory   disk   urls
notes-client   started           1/1         1G       1G     notes-with-typescript.cfapps.io
notes-server   started           1/1         1G       1G     notes-by-kotlin.cfapps.io

When you try to log in, you’ll get a CORS error.

Cloud Foundry Login

To fix this, log in to your Okta dashboard once more and navigate to Security > API > Trusted Origins. Add http://notes-with-typescript.cfapps.io as an Origin URL with CORs support. You’ll also need to add https://notes-with-typescript.cfapps.io as a Login Redirect URI to your “My OIDC App”.

You can now log in and add a note.

Success in Production

#Learn More

Congrats! You’re well on your way to becoming a Kotlin and TypeScript developer who understands Spring Boot and Angular. All of the code used in this article is available on GitHub.

Suggest

Angular 4 (previously Angular 2): The Pragmatic Guide

Web development with PHP : build a nice job recruitment site

Basic to Semantic HTML: How to Structure Web Pages

The Unreal Engine Developer Course – Learn C++ & Make Games

LEAVE A REPLY

Please enter your comment!
Please enter your name here