Photo by William Iven on Unsplash

This is the second part in a multi-chapter series outlining building a fully functional multi platform Progressive Web Application (PWA) for mobile and desktop.

Part 1: Build with Angular / Material, web application manifest, service worker, icons and add to home screen functionality. Deploy to AWS using the AWS Amplify CLI.

Part 2 (your are here): Adding Authentication with Amazon Cognito, Social Federation, and supporting individual user profile storage.

If you have not completed Part 1, you will need to refer to at least the setup portion to get the Amplify CLI and other requirements installed. To begin adding authentication to our PWA, we will use the Amplify CLI to generate an authentication configuration using Amazon Cognito User Pools. We will also add Storage with Amazon S3 for storing user profile avatars and profile information with custom attributes.

There is a fairly significant amount of boilerplate code involved in this section since it involves creating a custom Authenticator component from scratch. For this reason, I will run through how to generate the components and services within Angular based on part 1’s code, and then provide the details around the functionality within Angular.

The complete application code is available in partTwo of the open source Github Repository.

Important: Before you get started, be sure to run through the social federation section of the “Social Provider Setup” section of the aws-amplify documentation and retrieve the social configuration items that will be required by the CLI.

Beginning with the application code you completed in part one, from the root of your Angular application:

If you are starting from scratch you will need to run amplify init first. See the last section of Part One for steps on initializing the project.

$ amplify add auth ❯ Apply default configuration with Social Provider (Federation)

What domain name prefix you want us to create for you?

materialpwa

Enter your redirect signin URI :

http://localhost:4200/auth/

? Do you want to add another redirect signin URI

No

Enter your redirect signout URI:

http://localhost:4200/auth/signin

? Do you want to add another redirect signout URI

No

Select the identity providers you want to configure for your user pool:

Facebook, Google



Enter your Facebook App ID for your OAuth flow:

<facebook-app-id>

Enter your Facebook App Secret for your OAuth flow:

<facebook-app-secret>



Enter your Google Web Client ID for your OAuth flow:

<google-client-id>

Enter your Google Web Client Secret for your OAuth flow:

<google-client-secret>

Successfully updated resource cognitoXXXXXX locally What do you want to do? Apply default configuration with Social Provider (Federation)What domain name prefix you want us to create for you?Enter your redirect signin URI :? Do you want to add another redirect signin URIEnter your redirect signout URI:? Do you want to add another redirect signout URISelect the identity providers you want to configure for your user pool:Enter your Facebook App ID for your OAuth flow:Enter your Facebook App Secret for your OAuth flow:Enter your Google Web Client ID for your OAuth flow:Enter your Google Web Client Secret for your OAuth flow:Successfully updated resource cognitoXXXXXX locally

This will bootstrap your AWS Cloud environment using AWS CloudFormation, as well as create an Amazon Cognito User Pools and Federated Identities setup using some pre-baked best practices provided by the AWS Amplify team. The default settings include:

Email confirmation upon signup (you will receive a code via email which you will enter into the user interface).

It will not contain Mult-Factor Authentication (MFA). You can enable MFA if you want at any time by running amplify auth update and selecting the second option, or by enabling it via the AWS console (this will cause drift in your CloudFormation setup though, and if you update auth via the CLI it may get overwritten).

and selecting the second option, or by enabling it via the AWS console (this will cause drift in your CloudFormation setup though, and if you update auth via the CLI it may get overwritten). Social Federation with Amazon Cognito User Pools will be completely handled by the Amplify JS library.

Next, add Storage and choose Content (Images, audio, video, etc.) :

$ amplify add storage

? Please select from one of the below mentioned services (Use arrow keys)

❯ Content (Images, audio, video, etc.)

NoSQL Database

? Please provide a friendly name for your resource that will be used to label thi

s category in the project: (...) <press-enter>

? Please provide bucket name: (...) <press-enter>

? Who should have access: (Use arrow keys)

❯ Auth users only

Auth and guest users

? What kind of access do you want for Authenticated users (Use arrow keys)

read

write

❯ read/write

Now push your configuration to the cloud.

$ amplify push Current Environment: development | Category | Resource name | Operation | Provider plugin |

| -------- | --------------- | --------- | ----------------- |

| Auth | cognitoXXXXXXX | Create | awscloudformation |

| Storage | sXXXXXXXX | Create | awscloudformation |

? Are you sure you want to continue? Yes ...CloudFormation Logs... ✔ All resources are updated in the cloud

Once this completes a file is generated: src/aws-exports.js which will be used to access the Auth and Storage APIs within our Angular application. Next, from the root directory of the Angular application install the Amplify JavaScript library:

$ npm install --save aws-amplify

Even though we are installing the entire library, we will only be importing the Auth and Storage modules into our application. Amplify provides modular imports so that you can keep your application size low and access only the functionality you will be using from the library. The Amplify library utilizes the AWS JavaScript SDK under the covers. The AWS JavaScript SDK uses node types so you need to add these to the typescript configuration file.

Edit src/tsconfig.app.json and add “node” to the compiler option’s types array:

{

"extends": "../tsconfig.json",

"compilerOptions": {

"outDir": "../out-tsc/app",

"types": ["node"] // <-- add "node" here

},

"exclude": [

"test.ts",

"**/*.spec.ts"

]

}

We also need to add a polyfill to the src/polyfills.ts file since Angular has (in 6+) removed the reference to global within the framework, (*updated for Angular 8, which has also has removed the process property, another server-side JavaScript attribute) which is referenced by AWS SDK components since the AWS SDK for JavaScript is also supported in a node.js environment, while Angular is not.

Edit src/polyfills.ts and add the following line to the top of the file:

(window as any).global = window;

(window as any).process = {

env: { DEBUG: undefined },

};

If you have a live development server running, you will need to kill it and restart it at this point for the types to be compiled in at this point.

Import and configure the Amplify Framework in our Angular application by editing the src/main.ts file:

import 'hammerjs';

import { enableProdMode } from '@angular/core';

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';

import { environment } from './environments/environment'; // Amplify Configuration import Auth from '@aws-amplify/auth';

import Storage from '@aws-amplify/storage';

import AWSConfig from './aws-exports';

Storage.configure(AWSConfig);

Auth.configure(AWSConfig); // End Amplify Configuration if (environment.production) {

enableProdMode();

} platformBrowserDynamic().bootstrapModule(AppModule)

.catch(err => console.error(err));

Next we will use the Angular CLI to generate a set of view components that will be used to create a custom Authentication view. We will provide our users with the ability to login via Cognito User Pools with only email and password, create a new account, and verify their email address used when creating the new account. We will use Angular Route Guards to lock down our authenticated views, as well as not show our login/registration views when logged in. The guards will redirect appropriately based on authentication state. We will also use the Auth API directly with the Hub module in Amplify to listen to Authentication state.

The User Interface (UI) will utilize Angular Reactive Forms for form validation. The form components and services need to be add to the app.modules.ts. Copy the FormsModule and ReactiveFormsModule into the imports array.

imports: [

...

FormsModule,

ReactiveFormsModule,

...],

Now you will generate a number of application services and components. Each link/explanation below will link to the source code to copy into your application.

Create a directory for shared services that will be used across components within the application. Then generate the services using the Angular CLI (from the root of the application):

mkdir src/app/services

ng g service services/compressor

ng g service services/notification

Copy the code available on GitHub by following the links to each service. The compressor service is used to compress images prior to uploading them to S3. The notification service is used as a wrapper around the Angular Material Snackbar component to simplify the API usage and display temporary notifications in the application.

Next, generate components for the application views.

ng g component auth

ng g component loader --entryComponent

ng g service loader/loader

ng g component auth/countryCodeSelect --entryComponent

ng g class auth/country-code-select/countryCodes

ng g pipe auth/country-code-select/filter

ng g component auth/signIn

ng g component auth/signUp

ng g component auth/confirmCode

ng g component auth/profile

ng g component auth/profile/avatar

The source code for these components is available in the GitHub repository, click each component to retrieve it’s source code and copy/paste it’s contents into your application components.

Auth: The main component for handling Authentication routes and views. The auth component only serves as a router-outlet. Each sub-component contains the views for the authentication functionality i.e. signIn, signUp.

Loader: A component that displays a modal loading dialog with a material progress indicator and a service for showing/hiding the loader. The service can be injected into any component and provides the view toggling functionality that displays the UI.

The Angular environment will be used to pass the confirmation information (email/pass) to the confirm code component. This allows you to automatically login the user after confirmation. Update the environment ts files to include the following property object:

confirm: {

email: '',

password: ''

}

confirmCode: A UI for confirming an email address after signing up.

signIn: A UI for logging in a new user. Note: You will see an error if you are serving your application on these views until you implement the guards in the next section.

You will see an error if you are serving your application on these views until you implement the guards in the next section. signUp: A UI for users to create new accounts.

countryCodeSelect: A UI component for selecting a country code for a phone number. The component contains a view and an Angular pipe filter and a class for the country code data.

profile: A UI for displaying a user’s profile information stored as Cognito custom attributes.

avatar: A UI for for displaying and uploading a picture via file input, and drag/drop interfaces. The file is uploaded and stored in Amazon S3 on a per user basis with the Cognito Identity ID as the prefix.

You should now have the following directory structure within your application:

- src

- app

- auth

- confirm-code

- country-code-select

- profile

- avatar

- sign-in

- sign-up

- home

- ios-install

- loader

- material

We will use Angular guards to lock down views we only want available to logged in users, and also in the reverse situation, only views available to non-logged in users i.e. the Login/Registration views. From the src/app/auth directory run the following commands:

ng generate service auth/auth --module=app.module

ng generate guard auth/auth

? Which interfaces would you like to implement?

❯◉ CanActivate

◯ CanActivateChild

◯ CanLoad

ng generate guard auth/unauth

? Which interfaces would you like to implement?

❯◉ CanActivate

◯ CanActivateChild

◯ CanLoad

The source code for these files are available in the GitHub repository. Click each item to retrieve it’s source code and copy/paste it’s contents into your application components.

AuthService: A service wrapping the Amplify SignIn and SignUp APIs and exposing an Angular Observable tied to the Amplify Hub event dispatcher, providing authentication state change events.

AuthGuard: An Angular Router Guard used to check an authentication session prior to activating certain routes. In our application’s case, only the root \ router will require an authentication session in order to activate.

router will require an authentication session in order to activate. UnauthGuard: An Angular Router Guard used to check authentication session and display the SignIn/SignUp views when not logged into the application.

To enable the Route Guards reference them in the app-routing.module.ts file:

const routes: Routes = [

{ path: 'auth', component: AuthComponent, children: [

{

path: 'signin',

component: SignInComponent,

canActivate: [UnauthGuard]

},

{

path: 'signup',

component: SignUpComponent,

canActivate: [UnauthGuard]

},

{

path: 'confirm',

component: ConfirmCodeComponent,

canActivate: [UnauthGuard]

},

{

path: 'profile',

component: ProfileComponent,

canActivate: [AuthGuard]

}

]},

{ path: '', component: HomeComponent, canActivate: [AuthGuard] }];

Your application should now be ready to serve. For testing a PWA it’s easier to run the ng serve during development of UI etc. Since the service worker will cache these views making it difficult to live reload. Test your application now with:

ng serve

Your application should be available on port 4200. click on My Account (Part 2) in the menu and you should have a custom material designed Authenticator with dynamic form validation.

Sign in with Social Providers

Telephone with country code selector

When you register for a new account you will receive a confirmation screen to enter your code that is emailed to you.

When you enter this code, we use the information stored in the environment to automatically log the user in so they do not have to return to the sign-in screen. Once confirmed, you should be sent directly to the home view.

When navigating to something like sign-in or sign-up while logged in. the UnauthGuard will prevent the view from loading and automatically re-route the user to the profile view. The profile view will display the values entered during sign-up, and allow editing of them (custom resources in Cognito). It will also host the avatar component for uploading avatar images to S3. The avatar component supports using the file input, as well as dragging / dropping images onto the picture canvas.

Thanks for checking out the post! If you run into problems or bugs please open an issue in GitHub, Pull Requests are also welcome.