Angular 7: What's New and Noteworthy + OIDC Goodness

avatar-matt_raible.jpg Matt Raible

Angular 7 was released earlier this quarter and I’m pumped about a few of its features. If you’ve been following Angular since Angular 2, you know that upgrading can sometimes be a pain. There was no Angular 3, but upgrading to Angular 4 wasn’t too bad, aside from a bunch of changes in Angular’s testing infrastructure. Angular 4 to Angular 5 was painless, and 5 to 6 only required changes to classes that used RxJS.

Before I dive into showing you how to build an Angular app with authn/authz, let’s take a look at what’s new and noteworthy in this release.

Upgrade to Angular 7

If you created your app with Angular CLI, chances are you can easily upgrade to the latest release using ng update.

ng update @angular/cli @angular/core

You can also use the Angular Update Guide for complete step-by-step instructions.

Angular Update Guide

What’s New in Angular 7

There are a few notable features in Angular 7, summarized below:

  • CLI prompts: this feature has been added to Schematics so you can prompt the user to make choices when running ng commands.

  • Performance enhancements: the Angular team found many people were using reflect-metadata as a dependency (rather than a dev-only dependency). If you update using the aforementioned methods, this dependency will automatically be moved. Angular 7 also adds bundle budgets so you’ll get warnings when your bundles exceed a particular size.

  • Angular Material: Material Design had significant updates in 2018 and Angular Material v7 reflects those updates.

  • Virtual Scrolling: this feature allows you to load/unload parts of a list based on visibility.

  • Drag and Drop: this feature has been added to the CDK of Angular Material.

Bundle budgets is the feature that excites me the most. I see a lot of Angular apps with large bundle sizes. You want your baseline cost to be minimal, so this feature should help. The following defaults are specified in angular.json when you create a new project with Angular CLI.

"budgets": [{
  "type": "initial",
  "maximumWarning": "2mb",
  "maximumError": "5mb"
}]
You can use Chrome’s data saver extension for maximum awareness of the data your app uses.

For more details on what’s new in Angular 7, see the Angular blog, coverage on InfoQ, or the Angular project’s changelog.

Now that you know how awesome Angular 7 is, let’s take a look at how to create secure applications with it!

Create a Secure Angular 7 Application

An easy way to create Angular 7 apps is using the Angular CLI. To install it, run the following command:

npm i -g @angular/cli

The example below uses Angular CLI 7.1.0. To verify you’re using the same version, you can run ng --version.

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 7.1.0
Node: 11.1.0
OS: darwin x64
Angular:
...

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.11.0
@angular-devkit/core         7.1.0
@angular-devkit/schematics   7.1.0
@schematics/angular          7.1.0
@schematics/update           0.11.0
rxjs                         6.3.3
typescript                   3.1.6

To create a new app, run ng new ng-secure. When prompted for routing, type "Y". The stylesheet format is not relevant to this example, so choose whatever you like. I used CSS.

After Angular CLI finishes creating your app, cd into its directory, run ng new, and navigate to http://localhost:4200 to see what it looks like.

Default Angular App!

Add Identity and Authentication to Your Angular 7 App with OIDC

If you’re developing apps for a large enterprise, you probably want to code them to use the same set of users. If you’re creating new user stores for each of your apps, stop it! There’s an easier way. You can use OpenID Connect (OIDC) to add authentication to your apps and allow them all to use the same user store.

OIDC requires an identity provider (or IdP). There are many well-known IdPs like Google, Twitter, and Facebook, but those services don’t allow you to manage your users like you would in Active Directory. Okta allows this, and you can use Okta’s API for OIDC.

Register for a forever-free developer account, and when you’re done, come on back so you can learn more about how to secure your Angular app!

Free developer account!

Now that you have a developer account, I’ll show you several techniques for adding OIDC authentication to you Angular 7 app. But first, you’ll need to create a new OIDC app in Okta.

Create an OIDC App in Okta

Log in to your Okta Developer account and navigate to Applications > Add Application. Click Web and click Next. Give the app a name you’ll remember, and specify http://localhost:4200 as a Login redirect URI. Click Done. Edit your app after creating it and specify http://localhost:4200 as a Logout redirect URI too. The result should look something like the screenshot below.

Okta OIDC App

Use angular-oauth2-oidc

The angular-oauth2-oidc library provides support for OAuth 2.0 and OIDC. It was originally created by Manfred Steyer and includes many community contributions.

Install angular-oauth2-oidc using the following command:

npm i angular-oauth2-oidc@5.0.2

Open src/app/app.module.ts and import OAuthModule as well as HttpClientModule.

import { HttpClientModule } from '@angular/common/http';
import { OAuthModule } from 'angular-oauth2-oidc';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    OAuthModule.forRoot()
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Modify src/app/app.component.ts to import OAuthService and configure it to use your Okta application settings. Add login() and logout() methods, as well as a getter for the user’s name.

import { Component } from '@angular/core';
import { OAuthService, JwksValidationHandler, AuthConfig } from 'angular-oauth2-oidc';

export const authConfig: AuthConfig = {
  issuer: 'https://{yourOktaDomain}/oauth2/default',
  redirectUri: window.location.origin,
  clientId: '{yourClientId}'
};

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'ng-secure';

  constructor(private oauthService: OAuthService) {
    this.oauthService.configure(authConfig);
    this.oauthService.tokenValidationHandler = new JwksValidationHandler();
    this.oauthService.loadDiscoveryDocumentAndTryLogin();
  }

  login() {
    this.oauthService.initImplicitFlow();
  }

  logout() {
    this.oauthService.logOut();
  }

  get givenName() {
    const claims = this.oauthService.getIdentityClaims();
    if (!claims) {
      return null;
    }
    return claims['name'];
  }
}

Modify src/app/app.component.html to add Login and Logout buttons.

<h1>Welcome to {{ title }}!</h1>

<div *ngIf="givenName">
  <h2>Hi, {{givenName}}!</h2>
  <button (click)="logout()">Logout</button>
</div>

<div *ngIf="!givenName">
  <button (click)="login()">Login</button>
</div>

<router-outlet></router-outlet>

Restart your app and you should see a login button.

App with Login button

Click the login button, sign in to your Okta account, and you should see your name with a logout button.

App with name and Logout button

Pretty slick, eh?

Okta’s Angular SDK

You can also use Okta’s Angular SDK to implement the same functionality. You can start by installing it.

npm i @okta/okta-angular@1.0.7

Change app.module.ts to configure your Okta settings and import the OktaAuthModule.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { OktaAuthModule } from '@okta/okta-angular';

const config = {
  issuer: 'https://{yourOktaDomain}/oauth2/default',
  redirectUri: window.location.origin + '/implicit/callback',
  clientId: '{yourClientId}'
};

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    OktaAuthModule.initAuth(config)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

You might notice the redirect URI is a bit different than the previous one. For this to work, you’ll need to modify your Okta app and add http://localhost:4200/implicit/callback as a Login redirect URI.

Modify src/app/app-routing.ts to have a route for this path.

import { OktaCallbackComponent } from '@okta/okta-angular';

const routes: Routes = [
  {
    path: 'implicit/callback',
    component: OktaCallbackComponent
  }
];

Change app.component.ts to use the OktaAuthService to determine if the user is authenticated.

import { Component, OnInit } from '@angular/core';
import { OktaAuthService, UserClaims } from '@okta/okta-angular';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  title = 'ng-secure';
  isAuthenticated: boolean;
  email: string;

  constructor(public oktaAuth: OktaAuthService) {
  }

  async ngOnInit() {
    this.isAuthenticated = await this.oktaAuth.isAuthenticated();
    this.user = await this.oktaAuth.getUser();
    // Subscribe to authentication state changes
    this.oktaAuth.$authenticationState.subscribe( async(isAuthenticated: boolean)  => {
      this.isAuthenticated = isAuthenticated;
      this.user = await this.oktaAuth.getUser();
    });
  }
}

Then update app.component.html to use isAuthenticated and display the user’s name.

<h1>Welcome to {{ title }}!</h1>

<div *ngIf="isAuthenticated">
  <h2>Hi, {{user?.name}}!</h2>
  <button (click)="oktaAuth.logout()">Logout</button>
</div>

<div *ngIf="!isAuthenticated">
  <button (click)="oktaAuth.loginRedirect()">Login</button>
</div>

<router-outlet></router-outlet>

Restart your app and you should be able to log in to your app using Okta’s Angular SDK.

Use Authorization Code Flow

There is a new draft specification for OAuth called OAuth 2.0 for Browser-Based Apps. This was created by Okta’s own Aaron Parecki and contains an interesting clause.

The OAuth 2.0 Implicit grant authorization flow (defined in Section 4.2 of OAuth 2.0 [RFC6749]) works by receiving an access token in the HTTP redirect (front-channel) immediately without the code exchange step. The Implicit Flow cannot be protected by PKCE [RFC7636] (which is required according to Section 6), so clients and authorization servers MUST NOT use the Implicit Flow for browser-based apps.

Both angular-oauth2-oidc and Okta’s Angular SDK use implicit flow, the accepted practice prior to the recent discussion in Aaron’s draft specification. So how do you follow Aaron’s recommendation and use authorization code flow with PKCE in your Angular app? There are a couple options:

I tried using angular-oauth2-oidc-codeflow with Okta. I used the code from my angular-oauth2-oidc example above and found I only needed to change a few things (after installing it with npm i angular-oauth2-oidc-codeflow):

  1. Imports should be from 'angular-oauth2-oidc-codeflow'

  2. The login() method in AppComponent should be changed to use auth code flow.

    login() {
      this.oauthService.initAuthorizationCodeFlow();
    }

After making these changes, I tried to log in to my original SPA app. The error I received was unsupported_response_type. I tried creating a new Native app with PKCE, but it failed because angular-oauth2-oidc-codeflow does not send a code challenge.

In my Build a Desktop App with Electron and Authentication, I successfully used AppAuth and PKCE. The reason this works is because it’s a desktop app and doesn’t send an origin header. Okta’s token endpoint doesn’t allow CORS (cross-origin resource sharing), so it won’t work in a browser client.

We hope to fix this soon. As the industry evolves, we’ll do our best to keep our libraries current with best practices. In the meantime, we recommend you use a CSP (content security policy) to make sure 3rd party scripts don’t have access to your Angular app.

See 10 Excellent Ways to Secure Your Spring Boot Application to see how to add a CSP with Spring Boot.

You might also find Micah Silverman’s PKCE Command Line project interesting.

Limit Access Based on Group for Your Angular 7 App

If you’d like to show/hide components of your app based on a user’s group, you’ll need to add a "groups" claim to your ID token. Log in to your Okta account, navigate to API > Authorization Servers, click the Authorization Servers tab and edit the default one. Click the Claims tab and Add Claim. Name it "groups", and include it in the ID Token. Set the value type to "Groups" and set the filter to be a Regex of .*.

Now you can create an Angular directive to show/hide elements based on the user’s groups. There is currently an open issue that shows how you might go about doing this.

Control Access to Routes with an AuthGuard

Angular’s router documentation includes an example of how to create an AuthGuard to protect routes so they’re only available to authenticated users.

Okta’s Angular SDK ships with an OktaAuthGuard that you can use to protect your routes. It verifies there is a valid access token before allowing the user to navigate to the route. Below is an example of how to configure it in app-routing.module.ts.

import { OktaAuthGuard } from '@okta/okta-angular';

const routes: Routes = [
  { path: 'secure', component: MySecureComponent, canActivate: [OktaAuthGuard] }
]

You can implement a similar auth guard for angular-oauth2-oidc as shown in Angular Authentication with OpenID Connect and Okta in 20 Minutes.

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { OAuthService } from 'angular-oauth2-oidc';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {

  constructor(private oauthService: OAuthService, private router: Router) {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    if (this.oauthService.hasValidIdToken()) {
      return true;
    }

    this.router.navigate(['/']);
    return false;
  }
}

Angular 7 CLI Tutorial and Angular 7 CRUD with Spring Boot

Phew, that’s a lot of information about authentication with Angular 7! For more straightforward Angular content, I invite you to take a look at a couple tutorials I recently upgraded to Angular 7.

I updated a few of my tutorials to use Angular 7 recently.

In fact, I enjoyed playing with Angular 7 so much, I turned the basic CRUD app tutorial into a screencast!

JHipster and Angular 7

I’m a committer on a project called JHipster. JHipster allows you to generate a Spring Boot app with an Angular UI quickly and easily. The JHipster team upgraded to Angular 7 in its 5.6.0 release. You can create a JHipster app with Angular using a single JDL file. JDL stands for JHipster Domain Language.

To see JHipster in action, install it using npm i generator-jhipster and create an app.jh file with the following JDL.

application {
  config {
    baseName blog,
    applicationType monolith,
    packageName com.jhipster.demo.blog,
    prodDatabaseType mysql,
    cacheProvider hazelcast,
    buildTool maven,
    clientFramework angular,
    useSass true,
    testFrameworks [protractor]
  }
}
JHipster uses JWT authentication by default, but you can switch it to use OIDC for authentication pretty easily (hint: just add authenticationType oauth2 to app.jh).

Create a blog directory and run jhipster import-jdl app.jh inside of it. In a minute or two, you’ll have a fully functional (and well-tested) Spring Boot + Angular + Bootstrap app! If you want to add entities to CRUD, see this sample JDL.

The sample JDL mentioned uses React for its clientFramework. Make sure to change it to angular to use Angular 7.

If you’ve never heard of JHipster before, you should download the free JHipster Mini-Book from InfoQ! It’s a book I wrote to help you get started with hip technologies today: Angular, Bootstrap and Spring Boot. The 5.0 version was recently released.

Learn More About Angular 7, JHipster, and OAuth 2.0

I hope you’ve enjoyed learning about Angular 7 and how to add authn/authz to an Angular app. I’ve written a lot about Angular on this blog. See the following posts to learn more about this modern web framework.

If you enjoyed this post, follow us on social media { Twitter, Facebook, LinkedIn, YouTube } to know when we’ve posted other awesome content