Uploading files to AWS S3 Buckets

Well... I said that I was going to step away a little bit from AWS but seems like it wasn't that easy. Today, I'm writing about uploading files to S3 using a node.js backend service. 

This will be a short tutorial divided in two steps, first the front end development and then the backend work. so let's start.

Web site

I'm using an Angular application created with the Angular-cli and Bootstrap as the front-end framework to design the website, however, in this tutorial I'm not going to focus on how to setup all of this. For UI notifications, we are using ngx-toastr (if you don't know about it, look at my review here).

To create the file upload component and give some styles, I used the following code:

<div class="custom-file" style="width: auto;">
  <input type="file" class="custom-file-input" 
         accept="application/pdf"
         (change)="upload($event.target.files, fileInput)" 
         id="customPreReadFile" #fileInput/>
  <label class="custom-file-label" for="customPreReadFile">{{getFileName()}}</label>
</div>

As you can see, we are allowing only PDF files, but this restriction can be disabled or modified to meet your needs.

On the component code, we created two methods, the "upload" method called on the change event and "getFileName" to display an instruction text or the name of the file if one was already selected. The code for both methods is as follows:

upload(files: FileList, fileInput: any) {
  if(files[0].type.indexOf("pdf") === -1){
    this.toastr.error("The file selected is not a PDF.", "Error");
    fileInput.value = "";
    return;
  }
  this.toastr.info("Uploading file...");
  this.uploadService.uploadFile(files[0], this.identifier).subscribe(data => {
    this.toastr.success("File has been uploaded.", "Success");    
  });
}

getFileName(): string {
  var fileName = this.file ?
      this.file.name :
  'Upload File';
  return fileName;
}

The service method is the one that prepares the file to be sent to the node.js service as follows:

uploadFile(file: File, id: string): Observable<any> {
  const formData: FormData = new FormData();
  formData.append("file", file, '${id}/${file.name}');
  return this.httpClient.post(environment.apiEndPoint 
    + '/admin/upload/'
    + id, formData);
}

Node JS Service

Having configured all the required parts in the front end code, we need to adapt our Node JS service to receive the file. The service uses Express to configure the REST API, but we also use a package called formidable to process the form data sent from the Angular application easily. Similar to the Web Site section, I'm not focusing on how to setup the node service, but rather the exact code to process the file upload. 

Before digging into the code, I'll explain a little bit about what formidable does. In short, formidable parses the content of the form sent in the request and saves it to a local temporary location; from there, we can grab the file and do any logic we want with it.

The express endpoint code looks like this: 

 

var IncomingForm = require('formidable').IncomingForm;
var fs = require('fs');
router.post('/admin/upload/:id', function (req, res) {
    var id = req.params.id;
    var s3Uploader = new S3Uploader(req);
    var form = new IncomingForm();
    var fileName = "";
    var buffer = null;
    form.on('file', (field, file) => {
        fileName = file.name;
        buffer = fs.readFileSync(file.path);
    });
    form.on('end', () => {
        s3Uploader.uploadFile(fileName, buffer).then(fileData => {
          res.json({
            successful: true,
            fileData
          });
        }).catch(err => {
            console.log(err);
            res.sendStatus(500);
        });
    });
    form.parse(req);
});

Before moving to the next part to upload the file to S3, let's explain what we are doing here. After importing the necessary dependencies, inside of the request handler we are doing multiple things:

  1. Creating an instance of an "S3Uploader" helper to send the files to S3.
  2. Configuring the "IncomingForm" instance from formidable.
    1. Define an event handler when a file is processed by formidable that retrieves the file name and creates a buffer that we will send to the S3 service.
    2. Define an event handler when the form has been processed to call the upload file method in the S3 helper.
  3. Calling the parse method from Formidable to start the whole process.

The "S3Uploader" object has the following code:

var AWS = require('aws-sdk');
function S3Uploader(request) {
  var jwtToken = request ? request.headers.cognitoauthorization : null;
  let credentials = {
    IdentityPoolId: "<IDENTITY POOL ID>",
    Logins: {}
  };
  credentials.Logins['cognito-idp.<COGNITO REGION>.amazonaws.com/<USER POOL ID>'] = jwtToken;

  AWS.config.update({
    credentials: new AWS.CognitoIdentityCredentials(credentials, {
      region: "<COGNITO REGION>"
    }),
    region: "<S3 BUCKET REGION>"
  });

  let s3 = new AWS.S3();
  function uploadFile(key, file) {
    var s3Config = {
      Bucket: "<BUCKET NAME>",
      Key: key,
      Body: file
    };
    return new Promise((resolve, reject) => {
      s3.putObject(s3Config, (err, resp) => {
        if (err) {
          console.log(err);
          reject({success: false, data: err});
        }
        resolve({sucess: true, data: resp});
      })
    });
  }
}

If the first part about configuring the AWS SDK to use proper credentials, I invite you to read my post on how to manage credentials properly using Cognito or even an older post where I explain how to use Cognito and the Federated Identities to create users with roles that can access AWS resources. 

In short, what we are doing is, retrieving the authentication token generated by cognito when the user logs in so that we can configure the AWS SDK use the permissions from the user. 

After all that, we just need to instantiate an object to use the S3 APIs and send the data to the bucket.

If you have any comment, don't hesitate in contacting me or leaving a comment below. And remember to follow me on @cannyengineer to get updated on every new post.

Proper credentials management in AWS Cognito

A couple of days ago, I wrote how to combine Cognito, Angular and Node.js, and it was primarily focused on how to authenticate users using those technologies. However, a real application has way more use cases and features than just a simple authentication process. 

AWS provides many services such as databases, file storages, lambda expressions, among others. To connect to those services, Amazon provides at our disposal an SDK compressed in an NPM package called AWS-SDK (sounds logic, right?)

Today, I wanted to write how to use Cognito services to authorize users to use these services, instead of creating an IAM user and using the access keys and secret directly in the code; which is a bad practice, since you are actually uploading to your code the credentials to connect to your AWS account, so let's say that if another person finds them, it can do some pretty bad things to your account.

In order to apply security to an application using Cognito, we will need to configure the following resources:

  1. Cognito User pool
  2. Cognito Federated Identities
  3. Code in your Node.js service

The first step, is actually something I wrote two weeks ago, configuring the user pool was something we had to do in order to authenticate users, so for this post, I will skip that step but you can read all about it here. So, let's start from step 2 now.

Cognito Ferederated Identity

Objective: create a federated identity that will provide credentials to users to connect to the Amazon Web Services

On the Amazon console, go to the Security section and click on Cognito. On the screen that appears, click on "Manage Federated Identities" and then "Create new identity pool". Right after, the first step is to type the name of the identity pool. Also, mark the checkbox to allow unauthenticated identities.

Federated Identity - Step 1

Next, open the Authentication providers section and in the Cognito tab, type the user pool id and app client id created in the previous section.

Federated Identity - Step 2

After that, click on create pool and it will prompt you to create some roles to be used for the authenticated and unauthenticated users. Type the role name that suits best your needs and click on allow. We will update this roles later to match the policies that we need.

Click on Allow, and it will create the roles and redirect you to the dashboard screen for the identity pool.

Next, we will go IAM and modify the roles created to have the proper credentials to access our resources. So, on the Amazon console, go to the Security section and click on IAM. On the screen that appears, click Roles in the left pane and search for the roles that you want to update.

For the unatuhenticated users, the policy could reflect the following configuration:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:GetItem",
                "dynamodb:Scan",
                "dynamodb:Query"
            ],
            "Resource": "arn:aws:dynamodb:us-east-1:ACCOUNTID:table/TABLENAME"
        },
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:PutItem",
                "dynamodb:UpdateItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:us-east-1:ACCOUNTID:table/TABLENAME",
            ]
        }
    ]
}

The policy above, is an example of how it should look like, remember that it will differ depending on how you need each role to behave, so it can have more or less privileges than this example.

For the authenticated users, the policy could reflect the following configuration:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "cognito-idp:ListUsersInGroup",
                "cognito-idp:DescribeUserPool",
                "cognito-idp:AdminEnableUser",
                "cognito-idp:SignUp",
                "cognito-idp:AdminDisableUser",
                "cognito-idp:ChangePassword",
                "cognito-idp:AdminAddUserToGroup",
                "cognito-idp:AdminUpdateUserAttributes",
                "cognito-idp:AdminConfirmSignUp"
            ],
            "Resource": "arn:aws:cognito-idp:us-east-2:ACCOUNTID:userpool/USERPOOLID"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "cognito-identity:*",
            "Resource": "arn:aws:cognito-identity:us-east-2:ACCOUNTID:identitypool/us-east-2:IDENTITYPOOLID"
        },
        {
            "Sid": "VisualEditor2",
            "Effect": "Allow",
            "Action": "cognito-sync:*",
            "Resource": [
                "arn:aws:cognito-sync:us-east-2:ACCOUNTID:identitypool/IDENTITYPOOLID",
                "arn:aws:cognito-sync:us-east-2:ACCOUNTID:identitypool/IDENTITYPOOLID/identity/*",
                "arn:aws:cognito-sync:*:*:identitypool/*/identity/*/dataset/*"
            ]
        },
        {
            "Sid": "VisualEditor3",
            "Effect": "Allow",
            "Action": [
                "dynamodb:PutItem",
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:Scan",
                "dynamodb:Query",
                "dynamodb:UpdateItem"
            ],
            "Resource": "arn:aws:dynamodb:us-east-1:ACCOUNTID:table/TABLENAME"
        },
        {
            "Sid": "VisualEditor4",
            "Effect": "Allow",
            "Action": [
                "dynamodb:GetItem",
                "dynamodb:Scan",
                "dynamodb:Query"
            ],
            "Resource": "arn:aws:dynamodb:us-east-1:ACCOUNTID:table/TABLENAME"
        }
    ]
}

The authenticated users will have the ability to manage cognito users and perform CRUD operations over the dynamo tables. I have created some custom pages to manage the cognito users, that's why I'm allowing authenticated users to have this policy. I encountered many problems letting only administrators to manage this users, so that's why I ended up creating those screens. For example, one problem is that when an admin creates a user, it stays in a status named FORCE_CHANGE_PASSWORD, and it means that the user should, somehow, change the password by using an AWS-SDK command, so I had to create a screen to do that. Also, resetting a password needed an AWS-SDK and other use cases that at the end, it makes so much sense just to create the pages, and stop fighting Amazon, :)

Code in Node.JS service

Objective: modify node.js service to use authentication based on Cognito tokens.

Lastly, this will the fastest step, the trainer application will already have this applied, but just to ensure, validate that the AWS SDK configuration is being done in the following way:

function awsDynamo(configuration) {
  var docClient;
  var credentials = {
    IdentityPoolId: configuration.identityPoolId,
    Logins: {}
  };
  if (configuration.cognitoJwt) {
    credentials.Logins[`cognito-idp.${configuration.cognitoRegion}.amazonaws.com/${configuration.userPoolId}`] = configuration.cognitoJwt;
  }
  AWS.config.update({
    credentials: new AWS.CognitoIdentityCredentials(credentials, {
      region: configuration.cognitoRegion
    }),
    region: configuration.dynamoRegion
  });
  docClient = new AWS.DynamoDB.DocumentClient();
  /* More code */
}

In this block of code, we are sending a configuration object that have the identity pool id, user pool id, the jwt token generated from cognito (when the user has been authenticated) and the region for the cognito identity pool and the dynamo tables.

After all this steps, the service should connect correctly, however, in 1 of 2 ocassions I had to do one more configuration. Apparently, in IAM we need to allow trusted connectivity between the user pool and the identity pool, this is something that I never found in any documentation provided by Amazon, so I had to dig in other places.

And to be honest, this is something that I need to give credits to a guy called Alex Hague. I found the solution here. In case the link is not working, this was his solution, and when applied, it worked perfectly.

To check that the role you have assigned in Cognito Identity Pools (Federated Identities), has a trust relationship with the identity pool. Get the identity pool ID + the name of the role that isn't working. To do this, follow the next steps:

  1. Go to Cognito
  2. Select Manage Federated Identities
  3. Select the identity pool
  4. Click Edit identity pool (top right)
  5. Make a note of the identity pool ID
  6. Make a note of the name of the role that isn't working (e.g. Cognito_blahUnauth_Role

In IAM, check the trust relationship for the role. Ensure that the StringEquals condition value matches the identity pool ID. To do this:

  1. Go to IAM
  2. Click Roles
  3. Click the name of the role that you noted previously
  4. Click Trust relationships
  5. On the right under Conditions, check the StringEquals condition contains the identity pool Id that you noted previously .

Finally, when you fixed the relationship of trust, the service should authenticate and authorize any user that works with the application.

Thanks for reading up to here, hope it works for you and don't hesitate on leaving any comment!

Using Cognito, Angular and Node.js together (3/3)

Application configuration

Objective: Configure a Node.js service using Express.js to authenticate Cognito tokens.

As prerequisites, we are one npm package, so please install the one listed below:

  • cognito-express [1]: Cognito-express authenticates API requests by verifying the Json Web Tokens signatures generated by Amazon Cognito.

Configure application back-end

To secure the back-end application, we need to setup a middle-ware method that will be applied to all routings where it's needed and a configuration file that returns variables depending on the environment.

The configuration file will return 3 variables, the user pool identifier, the application client identifier and the region where those 2 resources are located in AWS. The code for it looks like this:

module.exports = (function (env) {
    switch (env) {
        case 'prod':
            return {
                userPoolId: 'us-east-2_XYZXYZXYS',
                clientId: 'ABCDEFGHIJKLMNOPQ123',
                region: 'us-east-2'
            };
        case 'uat':
            return {
                userPoolId: 'us-east-2_XYZXYZXYS',
                clientId: 'ABCDEFGHIJKLMNOPQ123',
                region: 'us-east-2'
            };
        default:
            return {
                userPoolId: 'us-east-2_XYZXYZXYS',
                clientId: 'ABCDEFGHIJKLMNOPQ123',
                region: 'us-east-2'
            };
    }
})(process.env.NODE_ENV);

The middle-ware method uses the configuration file and the cognito-express package installed before. This method will be executed in all routings to validate that the token provided is valid according to Cognito. In case the Cognito User Pool can't validate the token, this middle-ware method will return a 401 status code for the request. The code for this method looks like this:

const CognitoExpress = require("cognito-express");
const awsConfig = require('../helpers/awsConfig');

const cognitoExpress = new CognitoExpress({
    region: awsConfig.region,
    cognitoUserPoolId: awsConfig.userPoolId,
    tokenUse: "id",
    tokenExpiration: 3600000
});

function validateAdmin(req, res, next) {
    let accessTokenFromClient = req.headers.authorization;
 
    if (!accessTokenFromClient) return res.status(401).send("Access Token missing from header");

    cognitoExpress.validate(accessTokenFromClient, function (err, response) {
        if (err) return res.status(401).send(err);
        res.locals.user = response;
        next();
    });
}

module.exports = validateAdmin;

Finally, we will apply to the routings the middle-ware method using the "use" method from the ExpressJS router.

var express = require('express');
var router = express.Router();
var cognitoValidator = require('../helpers/cognitoValidator');

var dynamo = require('../helpers/dataService');
router.use(cognitoValidator);

router.get('/'
    , function (req, res) {
        dynamo.getData().then(data => {
            res.json(data);
        }).catch(err => {
            console.log(err);
            res.sendStatus(500);
        });
    });

module.exports = router;

Summary

After finishing all 3 parts of this tutorial, you should have completed the configuration to use Cognito in an Angular application with a Node.js backend service.

Using Cognito, Angular and Node.js together (2/3)

Application configuration

Objective: Configure an angular application to authenticate Cognito users.

As prerequisites, we are using several npm packages, so please install the ones listed below:

  • ngx-toastr [1]: to show messages to the user.
  • amazon-cognito-identity-js [2]
  • aws-sdk [3]

Configure application front-end

To secure the front-end application, we need to setup a couple of services and utilities first. Specifically, we will create:

  • An authentication service that will validate if there is a valid session
  • An authentication guard service that will be used by the Angular Router Module to allow requests
  • A token interceptor to detect all HTTP requests and attach an authentication token. This same interceptor will analyze responses and if there is an error, it will redirect to an appropriate screen.
  • A user service that will handle all the interactions with Cognito

The first authentication service uses a custom made Local Storage Service that is not shown in this tutorial, but basically, under the hood it will use some storage mechanism to persist the tokens and retrieve them.

The code for this service looks like this:

import { AdminLocalStorageService } from "./admin-local-storage.service";
import { Injectable } from "@angular/core";

@Injectable()
export class AuthenticationService {
    constructor(
        private localStorage: AdminLocalStorageService) {
    }

    isAuthenticated(){
        var token = this.localStorage.getToken();
        return token != null;
    }
}

The authentication guard is a special class that implements the CanActivate class from the angular router modules. This service will use our authentication service to verify that the session tokens exist, and if these tokens are missing, it will redirect the user automatically to the login screen.

The code for this service looks like this:

import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';
import { AuthenticationService } from './authentication.service';
import { ToastrService } from 'ngx-toastr';

@Injectable()
export class AuthGuardService implements CanActivate {

    constructor(private auth: AuthenticationService
        , private router: Router
        , private toastr: ToastrService) {}

    canActivate(): Promise<boolean> {
        return new Promise(resolve => {
            if(this.auth.isAuthenticated()){
                resolve(true);
            } else {
                this.toastr.error("Please login...", "Unauthorized");
                this.router.navigate(['/']);
                resolve(false);
            }
        });
    }
}

The token interceptor, as mentioned before, will be used by the application module to intercept all HTTP requests and attach the authentication token. Since we are doing authentication only, we are adding only one header to the requests that contains the ID token retrieved from Cognito.

The code for the interceptor looks like this:

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpResponse,
  HttpErrorResponse
} from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { Router } from '@angular/router';
import 'rxjs/add/operator/do';

import { AuthenticationService } from './services/authentication.service';
import { AdminLocalStorageService } from './services/admin-local-storage.service';
import { ToastrService } from 'ngx-toastr';


@Injectable()
export class TokenInterceptor implements HttpInterceptor {

  constructor(public auth: AuthenticationService
    , private localStorage: AdminLocalStorageService
    , private router: Router
    , private toastr: ToastrService) {}

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        const authData = this.localStorage.getToken();
        let requestItem = request;
        if (authData) {
            requestItem = request.clone({
                headers: request.headers.set("Authorization",
                    authData.jwtToken)
            });
        }
        return next.handle(requestItem).do((event: HttpEvent<any>) => {
            if (event instanceof HttpResponse) {
              //letting it pass
            }
        }, (err: any) => {
            if (err instanceof HttpErrorResponse) {
                if (err.status === 401) {
                    this.localStorage.deleteToken();
                    this.toastr.error("Please login again.", "Session ended");
                    this.router.navigate(['/']);
                }
            }
        });
    }
}

Finally, the last service needs two classes, the service that will be doing the authentication and a utils class that will help processing classes and attributes for Cognito.

The Cognito utils class looks like this:

import { IAuthenticationDetailsData, CognitoUserPool, CognitoUserAttribute, ICognitoUserAttributeData } from "amazon-cognito-identity-js";
import { environment } from "../../environments/environment";
import { AttributeListType } from "aws-sdk/clients/cognitoidentityserviceprovider";
import { AttributeType } from "aws-sdk/clients/elb";

export class CognitoUtils {
    public static getAuthDetails(email: string, password: string): IAuthenticationDetailsData {
        return {
            Username: email,
            Password: password,
        };
    }

    public static getUserPool() {
        return new CognitoUserPool(environment.cognitoSettings);
    }

    public static getAttribute(attrs: CognitoUserAttribute[], name: string): CognitoUserAttribute {
        return attrs.find(atr => atr.getName() === name);
    }

    public static getAttributeValue(attrs: AttributeListType, name: string, defValue: any): string {
        const attr = attrs.find(atr => atr.Name === name);
        return attr ? attr.Value : defValue;
    }

    public static getActiveAttribute(attrs: AttributeListType): boolean {
        return CognitoUtils.getAttributeValue(attrs, 'custom:active', '1') === '1';
    }

    public static createNewUserAttributes(request): CognitoUserAttribute[] {
        const emailAttribute = new CognitoUserAttribute({Name : 'email', Value : request.email });
        const emailVerifiedAttribute = new CognitoUserAttribute({Name : 'email_verified', Value : 'true' });
        const activeAttribute = new CognitoUserAttribute({Name : 'custom:active', Value : (request.active ? 1 : 0).toString() });
        return [
            emailAttribute, activeAttribute
        ];
    }

    public static createUpdatableUserAttributesData(request): AttributeListType {
        const preferedUsername = {Name : 'preferred_username', Value : request.username };
        const emailAttribute = {Name : 'email', Value : request.email };
        const emailVerifiedAttribute = {Name : 'email_verified', Value : 'true' };
        const activeAttribute = {Name : 'custom:active', Value : (request.active ? 1 : 0).toString() };
        return [
            preferedUsername, emailAttribute, emailVerifiedAttribute,
            activeAttribute
        ];
    }
}

And the code for the service looks like this:

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { HttpClient, HttpHeaders } from '@angular/common/http';

import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer';
import 'rxjs/add/observable/from.js';
import { IntervalObservable } from 'rxjs/observable/IntervalObservable';
import { CognitoUserSession, CognitoUserPool, CognitoUser, AuthenticationDetails } from 'amazon-cognito-identity-js';
import * as AWS from 'aws-sdk';
import { ListUsersRequest } from 'aws-sdk/clients/cognitoidentityserviceprovider';

import { environment } from '../../../environments/environment';
import { CognitoUtils } from '../cognitoUtils';
import { User } from '../models/user';
import { AdminLocalStorageService } from './admin-local-storage.service';

@Injectable()
export class UsersService {
    session: CognitoUserSession;
    cognitoAdminService: AWS.CognitoIdentityServiceProvider;
    userPool: CognitoUserPool;

    constructor(private http: HttpClient, private router: Router, private adminLocalStorage: AdminLocalStorageService) {
        this.cognitoAdminService = new AWS.CognitoIdentityServiceProvider({
            accessKeyId: environment.awsConfig.accessKeyId,
            secretAccessKey: environment.awsConfig.secretAccessKey,
            region: environment.awsConfig.region
        });
        this.userPool = CognitoUtils.getUserPool();
    }

    public login(login: string, password: string): Observable<User | false> {
        const cognitoUser = new CognitoUser(this.getUserData(login));
        cognitoUser.setAuthenticationFlowType('USER_PASSWORD_AUTH');
        const authenticationDetails = new AuthenticationDetails(CognitoUtils.getAuthDetails(login, password));
        return Observable.create(obs => {
            cognitoUser.authenticateUser(authenticationDetails, {
                onSuccess: result => {
                    this.session = result;
                    const token = result.getIdToken();
                    const accessToken = result.getAccessToken();
                    this.adminLocalStorage.setToken(token);
                    this.adminLocalStorage.setAccessToken(accessToken);
                    this.router.navigate(['managereps']);
                },
                onFailure: err => {
                    console.error(err);
                    obs.next(false);
                },
                newPasswordRequired: (userAttributes, requiredAttributes) => {
                    this.router.navigate(['dashboard/login', { username: login }]);
                    obs.next(false);
                }
            });
        });
    }

    private getUserData(email: string) {
        return {
            Username: email,
            Pool: this.userPool
        };
    }

    public addUser(newUser: User): Observable<Object> {
        return Observable.create(obs => {
            const attrs = CognitoUtils.createNewUserAttributes(newUser);
            const cognitoUser = new CognitoUser(this.getUserData(newUser.username));
            this.userPool.signUp(newUser.username, newUser.password, attrs, [], (error, data) => {
                    if (error) {
                        console.error(error);
                        obs.next(false);
                        return;
                    }
                    this.cognitoAdminService.adminConfirmSignUp({
                        Username: newUser.username,
                        UserPoolId: this.userPool.getUserPoolId()
                    }, (e, d) => this.defaultAdminCallback(e, d, obs));
            });
        });
    }

    private defaultAdminCallback(error, data, obs, ok: any = true, no: any = false) {
        if (error) {
            console.error(error);
            obs.next(no);
            return;
        }
        obs.next(ok);
    }
}

As you can see, we have 2 main public methods, one for login and the other one to create users. The user class reference  above, it's just a class with 3 properties for username, password and email.

The login method is used in a login page that accepts the credentials used for authentication. And the addUser method is used in a sign up page for users. Both of these pages are not provided in this tutorial. 

Finally, we need to add the interceptor and the guard service to the application module. Note: since this tutorial was created using a small application, only the app module was created, however, the interceptor module must be applied in any module that the HTTPClientModule is imported and the guard must be applied in any routing configuration.

To apply the guard configuration in the module, we override the canActivate property for the routes as shown next:

const routes: Routes = [
  {
    path: 'page1', component: Page1Component,
    canActivate: [AuthGuardService]
  },
  { path: '', component: LoginComponent }
];

For the interceptor, we need to add it to the providers property configuration as shown next:

providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: TokenInterceptor,
      multi: true
    },
    AdminLocalStorageService,
    AuthenticationService,
    AuthGuardService,
    UsersService
  ]

With this, the configuration for the angular application is complete, and it should be ready for testing. So, finish creating the login and sign up component and start authenticating users.

Continue with the third part of this tutorial here

Using Cognito, Angular and Node.js together (1/3)

Today, I'm writing a new tutorial after two weeks of continuing working on AWS. However, this time I'm going to dive a little bit more on development than the last time. 

This time, I would like to explain how to use Cognito, Angular and Node.js together. The first one to be used as user and access management system, Angular as the framework for the front end application, and obviously, Node.js as a backend service using Express.js also.

Cognito

Objective: create a user pool to sign-up and authenticate users

The first thing we need to configure is a User Pool that will be used as a user directory. This directory will allow us sign up and sign in users. 

User Pools also provide integration with third party providers such as Facebook, Google, Amazon, and Microsoft Active Directory. [1]

Amazon Cognito - User Pools

In this tutorial, we won't enable third party integration with any provider. Our user pool will contain our users and some information such as the email, user name, password and an active indicator.

Creating the user pool

On the Amazon console, go to the Security section and click on Cognito. On the screen that appears, click on "Manage User Pools" and then "Create User Pool".

Right after, the first step is to type the name of the user pool. 

User Pool Creation - Type name

After typing the name, click on the "Step Through Settings" option, we will go step by step through all the configuration.

The next section will ask us to select the attributes for our users. In there, select the option to use an username to sign up and sign in, and mark the check boxes to sign in with a verified email address and preferred username. This two options will provide flexibility for our end users and validate that they are using valid email addresses. 

User Pool Creation - Attributes

Below this section, you can add more attributes that will be used for the user profile, for example zip code, among others. Also, there is the possibility to add custom attributes that are not supported out of the box.

Continuing with the wizard, the next section that we will configure are the policies. These policies will specify the level of security of the password, for our case we will leave all options by default.

Also, we will allow users to sign themselves in the User Pool. Note: This is important, if we allow only administrators to create users, Amazon leaves users with a FORCE_CHANGE_PASSWORD state that is not easily changed, therefore, we will use the AWS SDK later to add users manually without the console.

User Pool Creation - Policies

Continue with the wizard and in the MFA and Verification section, create a role that will be used automatically by AWS to send SMS messages. Even though this is not going to be used in our tutorial, it's better to set this up right away. Note: the role created here is not the same to the roles we can assign to our users. To assign roles to a user, first you must create a group after the user pool is created, and assign roles to that group. Then, all the users that belongs to a particular group, will inherit those roles.

User Pool Creation - MFA and Verifications

The rest of the options were left with the default values. Continuing with the wizard, we only have two more sections that we are interested in. Navigate until reaching the Message Customizations section and change the verification type radio button to use links instead of codes. The only difference you will see is that the emails sent to users will contain a Url to verify the account, instead of using a pass code to be validated somewhere else (this will make the signup process much faster).

User Pool Creation - Message Customizations

Finally, navigate to the App Client section in the wizard, this will be a key part in the creation of the user pool because this will provide us the identification keys that our application will use to validate users.

Click on Create App Client and type a client name. Leave the Generate Client Secret unchecked, otherwise authentication does not work as expected. Besides, mark the enable username-password for app-based authentication, this feature allows our application to use a combination of username and password to authenticate our users. Click again on Create App Client and finish creating the user pool. Note: the last section of the wizard will show a review of all the configurations added through the wizard, if there is something that needs to be changed, change it now. 

User Pool Creation - App Client

Continue with the second part of this tutorial here