Showing posts with label Angular. Show all posts
Showing posts with label Angular. Show all posts

Pythonizing our Web App!

Ok, you asked for it! You all wanted an update for the lambda functions to use another language and
luckily, my favourite one! Python!

So let's get started!

Update the Lambda function to fetch and store data

The first step is to update the Lambda function that fetches data from an external website and stores it in a serverless database. Here is an example of the same function written in Python:

import boto3
import requests
dynamo = boto3.client('dynamodb')
def lambda_handler(event, context):
    # Fetch data from external website
    response = requests.get('https://example.com/data')
    data = response.json()
    
    # Store data in DynamoDB table
    item = {
        'id': {'S': data['id']},
        'data': {'S': data['data']}
    }
    dynamo.put_item(TableName='your-table-name', Item=item)
    return {
        'statusCode': 200,
        'body': json.dumps({'message': 'Data stored in DynamoDB'})
    }

This example uses the boto3 package to interact with DynamoDB and the requests package to fetch data from an external website, similar to the previous example. Note that the function uses python json module to parse data instead of JSON.parse() function in node.js

Update the Lambda function to retrieve data

The next step is to update the Lambda function that retrieves data from the serverless database and makes it accessible through the API Gateway. Here is an example of the same function written in Python:


import boto3
import json
dynamo = boto3.client('dynamodb')
def lambda_handler(event, context):
    # Retrieve data from DynamoDB table
    data = dynamo.scan(TableName='your-table-name')
    # Return data to user
    return {
        'statusCode': 200,
        'body': json.dumps(data)
    }

This function uses the boto3 package to interact with DynamoDB and returns the data to the user as a JSON string using json.dumps()

Re-Creating a Lambda Authorizer

A Lambda authorizer is a Lambda function that you build to authorize access to your API Gateway APIs using bearer token authentication, such as OAuth or JWT.

Here's an example of a simple Lambda authorizer function that can be used to authenticate a user using a bearer token. This function will use the Okta library to validate the token and return the appropriate policy allowing or denying access to the API Gateway endpoint.

import json
from okta.framework import OktaAuth
def lambda_handler(event, context):
    auth = OktaAuth(event)
    claims = auth.verify_token()
    if claims:
        auth_response = auth.get_auth_response(claims)
    else:
        auth_response = auth.get_auth_response(None, 'Unauthorized')
    return auth_response

This example uses the okta library to validate the token passed in the Authorization header and return the appropriate policy allowing or denying access to the API Gateway endpoint.

Updating Terraform Configuration

For the terraform configuration to use the python function in the Lambda function resource the runtime attribute needs to be updated from nodejs14.x to python3.8. Additionally, in order to link the newly created Lambda authorizer function to the API Gateway, we need to update the authorization attribute of aws_api_gateway_method resource to CUSTOM and add authorizer_id to it pointing to our newly created Lambda function.

resource "aws_api_gateway_method" "example" {
  rest_api_id = aws_api_gateway_rest_api.api_name.id
  resource_id = aws_api_gateway_resource.example.id
  http_method = "GET"
  authorization = "CUSTOM"
  authorizer_id = aws_lambda_authorizer.authorizer_lambda.id

  request_parameters = {
    "method.request.header.Authorization": true
  }
  integration {
    type = "AWS_PROXY"
    uri = aws_lambda_function.data_lambda.invoke_arn
    http_method = "POST"
    request_templates = {
      "application/json" = "{\"statusCode\": 200}"
    }
  }
}

Even Re-Create a Hello World Lambda Function

To create a simple "Hello World" Lambda function, you can use the following code:

def lambda_handler(event, context):
    message = 'Hello World!'
    return {
        'statusCode': 200,
        'body': json.dumps(message)
    }

This function returns a JSON object containing the message "Hello World!"

Verify the Deployment

To verify that everything is set up correctly, you can test the API Gateway endpoint using a tool like Postman and check the logs of all Lambda functions and the DynamoDB table to ensure that the data is being fetched and stored correctly, accessible when the user is logged in, and that the authorizer is properly handling token validation.

Additionally, you can test the "Hello World" lambda function by invoking it from the AWS Lambda console or by making a request to it via an API Gateway endpoint.

Wrap Up!

There you have it! You wanted it you got it, but by updating these Lambda functions and the Terraform configuration to use Python instead of Node.js, you can continue to use the same functionality as before but with a different programming language.

Despite updating the dependencies and language, the function code and dependencies together, remains ready for deployment with Terraform to create the same product demonstrating how modular we can make these websites!

m04r Data for the S3 App!

Now in our journey we need to add more tasks to build the front end. In this blog post, we will build out two Lambda functions and a serverless database to fetch and store data from an external website and make it accessible to a logged-in user through an API Gateway and by doing this we can no have more data available to our application out of the users raw reach and safely behind our API that will deal with any connection or credential issues.

Create a Lambda function to fetch and store data

The first step is to create a Lambda function that periodically fetches data from an external website and stores it in a serverless database. One way to achieve this is to set up a CloudWatch event to trigger the Lambda function on a regular schedule.

Here's an example of a Lambda function written in Node.js that fetches data from an external website using the request package and stores it in a DynamoDB table:

const AWS = require('aws-sdk');

const request = require('request');
const dynamo = new AWS.DynamoDB();
exports.handler = async (event) => {
    // Fetch data from external website
    const response = await request.get('https://example.com/data');
    const data = JSON.parse(response.body);
    // Store data in DynamoDB table
    const params = {
        Item: {
            "id": { S: data.id },
            "data": { S: data.data }
        },
        TableName: "your-table-name"
    };
    await dynamo.putItem(params).promise();
    return {
        statusCode: 200,
        body: JSON.stringify({
            message: 'Data stored in DynamoDB',
        }),
    };
};

This function uses the request package to fetch data from an external website, parses it as JSON, and stores it in a DynamoDB table using the AWS.DynamoDB client. The name of the table and the structure of the data being stored will depend on the external website you are fetching data from.

Create a Lambda function to retrieve data

The next step is to create a Lambda function that retrieves data from the serverless database and makes it accessible through the API Gateway. This function should be triggered when a user makes a request to a specific endpoint, such as /data.

Here's an example of a Lambda function written in Node.js that retrieves data from a DynamoDB table and returns it to the user:

const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB();
exports.handler = async (event) => {
    // Retrieve data from DynamoDB table
    const params = {
        TableName: "your-table-name"
    };
    const data = await dynamo.scan(params).promise();
    // Return data to user
    return {
        statusCode: 200,
        body: JSON.stringify(data),
    };
};

This function retrieves data from the DynamoDB table by scanning the table and returns it to the user as a JSON string.

Create a serverless database

We will use DynamoDB as the Serverless database and use terraform to create the DynamoDB table for storing the data.

resource "aws_dynamodb_table" "example" {
  name = "your-table-name"
  billing_mode = "PAY_PER_REQUEST"
  hash_key  = "id"
  attribute {
    name = "id"
    type = "S"
  }
  attribute {
    name = "data"
    type = "S"
  }
}

Integrate with API Gateway

We will need to integrate the Lambda function created on step 2 to the API Gateway so that it is triggered when a user makes a request to a specific endpoint. This can be done by creating a new resource and method in the API Gateway and linking it to the Lambda function.

Here's an example of how to create a new resource and method in the API Gateway using Terraform:

resource "aws_api_gateway_resource" "example" {
  rest_api_id = aws_api_gateway_rest_api.api_name.id
  parent_id = aws_api_gateway_rest_api.api_name.root_resource_id
  path_part = "data"
}
resource "aws_api_gateway_method" "example" {
  rest_api_id = aws_api_gateway_rest_api.api_name.id
  resource_id = aws_api_gateway_resource.example.id
  http_method = "GET"
  authorization = "NONE"
  request_parameters = {
    "method.request.header.Authorization": true
  }
  integration {
    type = "AWS_PROXY"
    uri = aws_lambda_function.data_lambda.invoke_arn
    http_method = "POST"
    request_templates = {
      "application/json" = "{\"statusCode\": 200}"
    }
  }
}

This Terraform code creates a new resource and method in the API Gateway, with the path /data and linked to the Lambda function created on step 2. This will trigger the Lambda function when a user makes a GET request to the /data endpoint and return the data stored in the DynamoDB table.

Verify the Deployment

To verify that everything is set up correctly, you can test the API Gateway endpoint using a tool like Postman and check the logs of both Lambda functions and the DynamoDB table to ensure that the data is being fetched and stored correctly and accessible when the user is logged in.

Wrap Up!

Now by creating these two Lambda functions and a serverless database to our setup, you can fetch and store data from an external website or another service and make it accessible to a our user through an API Gateway using what we learned from other posts we could use the logic to make sure our users are logged in with an identity as well!

I got ID! Adding Authorisation to our App!

Why not show off a bit of my fondness towards Pearl Jam?

In our previous chapters of this series we have:

That's right! Today we will add an additional layer of security to our Angular application by integrating an AWS Lambda Authorizer as an authentication and authorization method, using either AWS Cognito or Okta as a third-party provider.

Create a Lambda Authorizer function

The first step in creating a Lambda Authorizer is to create the Lambda function itself. The function will handle the authentication and authorization process by validating the incoming token and determining if the user is authorized to access the resources.

Here's an example of a simple Lambda Authorizer function written in Node.js that uses AWS Cognito as the third-party provider:

const AWS = require('aws-sdk');
const cognito = new AWS.CognitoIdentityServiceProvider();
exports.handler = async (event) => {
    // Extract the token from the request
    const token = event.authorizationToken;
    
    // Verify the token using the Cognito provider
    const params = {
        AccessToken: token
    };
    const data = await cognito.getUser(params).promise();
    
    // Determine if the user is authorized
    const user = data.UserAttributes;
    const isAuthorized = user.some(attr => attr.Name === 'custom:groups' && attr.Value === 'admin');
    
    // Return the policy statement
    if (isAuthorized) {
        return {
            principalId: user.find(attr => attr.Name === 'sub').Value,
            policyDocument: {
                Version: '2012-10-17',
                Statement: [
                    {
                        Action: 'execute-api:Invoke',
                        Effect: 'Allow',
                        Resource: event.methodArn
                    }
                ]
            }
        };
    } else {
        return {
            principalId: 'user',
            policyDocument: {
                Version: '2012-10-17',
                Statement: [
                    {
                        Action: 'execute-api:Invoke',
                        Effect: 'Deny',
                        Resource: event.methodArn
                    }
                ]
            }
        };
    }
};

If you are using Okta as the provider, you should use Okta's SDK and API to verify the token and determine if the user is authorized.

const OktaJwtVerifier = require('@okta/jwt-verifier');
const oktaJwtVerifier = new OktaJwtVerifier({
  issuer: 'https://{yourOktaDomain}.com/oauth2/default',
  clientId: '{clientId}'
});
exports.handler = async (event) => {
    // Extract the token from the request
    const token = event.authorizationToken;
    
    // Verify the token using the Okta provider
    try {
        const jwt = await oktaJwtVerifier.verifyAccessToken(token);
        // Verify that the user has the required scope
        if (jwt.claims.scp.split(" ").includes("admin")) {
            return {
                principalId: jwt.claims.sub,
                policyDocument: {
                    Version: '2012-10-17',
                    Statement: [
                        {
                            Action: 'execute-api:Invoke',
                            Effect: 'Allow',
                            Resource: event.methodArn
                        }
                    ]
                }
            };
        }
    } catch (err) {
        console.log(err);
    }
    // Return a policy statement denying access
    return {
        principalId: 'user',
        policyDocument: {
            Version: '2012-10-17',
            Statement: [
                {
                    Action: 'execute-api:Invoke',
                    Effect: 'Deny',
                    Resource: event.methodArn
                }
            ]
        }
    };
};

This example uses the @okta/jwt-verifier package to verify the access token and check for the required scope(in this case "admin").

Create the Lambda Authorizer on API Gateway

Once the Lambda Authorizer function is created, it needs to be added as an Authorizer on the API Gateway. This can be done using the AWS Management Console or using Terraform. 

resource "aws_api_gateway_authorizer" "authorizer_name" {
  name = "your-authorizer-name"
  rest_api_id = aws_api_gateway_rest_api.api_name.id
  type = "TOKEN"
  identity_source = "method.request.header.Authorization"
  authorizer_uri = "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/${aws_lambda_function.authorizer_name.arn}/invocations"
}

The above code creates an Authorizer on the API Gateway and associates it with the Lambda function that we created at the start.

Update the Angular application

Once the Lambda Authorizer is set up, the Angular application needs to be updated to send the token in the Authorization header with each request. This can be done by adding an interceptor that adds the token to the headers before sending the request.

Here's an example of an Angular interceptor that adds the token to the headers:

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { TokenService } from './token.service';
import { Observable } from 'rxjs';
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
    constructor(private tokenService: TokenService) {}
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        request = request.clone({
            setHeaders: {
                Authorization: `Bearer ${this.tokenService.getToken()}`
            }
        });
        return next.handle(request);
    }
}

This example assumes that you have a TokenService that retrieves the token from storage.

You will also need to register this interceptor in the app.module.ts file.

import { TokenInterceptor } from './token.interceptor';
providers:
...
@NgModule({
  ...
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: TokenInterceptor,
      multi: true
    }
  ],
  ...
})
export class AppModule { }

This registers the interceptor in the AppModule, ensuring that the token is added to the headers of all HTTP requests made by the application.

Deploy the Terraform Configuration

After updating the Angular application, the Terraform configuration can be deployed to create and configure the necessary resources on AWS.
$ terraform init
$ terraform apply

Verify the Deployment

Now we can test to make sure that the Lambda Authorizer has been set up correctly, you can test the API Gateway endpoint using a tool like Postman and check the logs of the Lambda function to ensure that it is being invoked correctly.

Wrap Up!

By now you should be starting to see how we are building up this application and how you can start to implement your own logic into this magical paradigm. By using a Lambda Authorizer and integrating with a third-party provider like AWS Cognito, Okta or even many others! you can add an additional layer of security to your Angular application.

Getting some Dynamic Data with help from Lambda

In our previous blog posts we have seen how we can reduce costs by growing our web presence using very cheap tooling such as S3 and Javascript to do more with our websites, in reality we could use any Javascript framework to borrow our users CPU to run the website but in this guide we have selected Angular.

Create an AWS Lambda function

The first step in adding a serverless backend to your Angular application is to create an AWS Lambda function. Lambda is a serverless compute service that allows you to run code without provisioning or managing servers.

resource "aws_lambda_function" "function_name" {
  function_name = "your-function-name"
  runtime = "nodejs12.x"
  handler = "index.handler"
  role = aws_iam_role.iam_role.arn
  environment {
    variables = {
      your_env_variable = "your-value"
    }
  }
  code {
    zip_file = file("path/to/your-lambda-function.zip")
  }
}

And inside this zip file we might want to hold some code to run...

exports.handler = async (event) => {
    return {
        statusCode: 200,
        body: JSON.stringify({
            message: 'Hello World',
        }),
    };
};


This function simply returns an object containing a statusCode of 200 and a message body containing a JSON string of the message "Hello World" but it would be SO much more! 

Create an IAM Role

In order to execute the Lambda function, an IAM role with the necessary permissions must be created. This role can be created using Terraform.

resource "aws_iam_role" "iam_role" {
  name = "your-role-name"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}
resource "aws_iam_role_policy" "iam_role_policy" {
  name = "your-role-policy"
  role = aws_iam_role.iam_role.id
  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:*"
      ],
      "Resource": "*"
    }
  ]
}
EOF
}

Create a Lambda API Gateway

Once the Lambda function is created, it needs to be exposed through an API Gateway. The API Gateway will handle the incoming requests and invoke the Lambda function.

resource "aws_api_gateway_rest_api" "api_name" {
  name = "your-api-name"
}
resource "aws_api_gateway_resource" "resource_name" {
  rest_api_id = aws_api_gateway_rest_api.api_name.id
  parent_id = aws_api_gateway_rest_api.api_name.root_resource_id
  path_part = "your-resource-path"
} 
resource "aws_api_gateway_method" "method_name" {
  rest_api_id = aws_api_gateway_rest_api.api_name.id
  resource_id = aws_api_gateway_resource.resource_name.id
  http_method = "GET"
  authorization = "NONE"
}
resource "aws_api_gateway_integration" "integration_name" {
  rest_api_id = aws_api_gateway_rest_api.api_name.id
  resource_id = aws_api_gateway_resource.resource_name.id
  http_method = aws_api_gateway_method.method_name.http_method
  type = "AWS"
  uri = "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/${aws_lambda_function.function_name.arn}/invocations"
  integration_http_method = "POST"
}
resource "aws_api_gateway_deployment" "deployment_name" {
  rest_api_id = aws_api_gateway_rest_api.api_name.id
  stage_name = "prod"
}

Deploy the Terraform Configuration

After updating the Angular application, the Terraform configuration can be deployed to create and configure the necessary resources on AWS.

terraform init

terraform apply

Verify the Deployment

As always we should verify what we deploy, to make sure that the serverless backend has been set up correctly, you can test the API Gateway endpoint using a tool like Postman, and check the logs of the Lambda function to ensure that it is being invoked correctly.

Updating the Angular Application

Now that the Lambda function and API Gateway have been created, the Angular application needs to be updated to call the API Gateway endpoint. This can be done by adding an HTTP service in the Angular application, and making a GET request to the API Gateway endpoint.

In order to consume this data in your Angular application, you will need to create an Angular module that makes an HTTP request to the API Gateway endpoint and handle the response data. You can use the HttpClient module from @angular/common/http to make the request.

Here's an example of an Angular service that makes a GET request to the API Gateway endpoint and returns the data:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
  providedIn: 'root'
})
export class HelloWorldService {
  constructor(private http: HttpClient) { }
  getHelloWorld() {
    return this.http.get<any>('https://your-api-gateway-endpoint.execute-api.us-east-1.amazonaws.com/prod/your-resource-path');
  }
}

You can then import this service in your component class and call the getHelloWorld() method to retrieve the data like in the following example:

import { Component, OnInit } from '@angular/core';
import { HelloWorldService } from './hello-world.service';
@Component({
  selector: 'app-root',
  template: '<p> {{ message }} </p>'
})
export class AppComponent implements OnInit {
  message: string;
  constructor(private helloWorldService: HelloWorldService) {}
  ngOnInit() {
    this.helloWorldService.getHelloWorld().subscribe((data) => {
        this.message = data.message;
    });
  }
}

This component will now display the message "Hello World" on the webpage when implemented.

It's important to notice that you should replace the url of the API Gateway on the service file with the url of your API Gateway after you deploy it with terraform, also you will need to import HttpClientModule in the root module of your Angular application and import the service in the components that you want to consume the data.

Wrap Up!

As you can see, integrating a serverless backend using AWS Lambda and API Gateway is a great way to add dynamic functionality to your Angular application while also taking advantage of the scalability and cost-efficiency of serverless compute.

By using Terraform, we can automate the process of creating and configuring the necessary resources, making the deployment process faster and more efficient. Remember to verify the backend and the frontend are correctly deployed, and test the API Gateway endpoint to check the Lambda function is being invoked correctly.

Upgrading your S3 SPA with Angular

Hey! I am a poet! In our previous blog post we built a very cheap and cost effective website on S3 using Terraform, if you
haven't seen it you can find it here. Going a Step further we can do some clever things with this rather cheap concept. For example, why don't we upgrade this setup to add some more complex logic and turn it into a Web Application?

To accomplish this, let's look at some Javascript frameworks. We sadly need to use Javascript as S3 has no server side processing capabilities, in the future we will look at how we can accomplish tasks that require this but for now, let's built a WebApp with Angular, (not to be confused with AngularJS which uses much older concepts and beyond he scope of this post).

Deploying an Angular application to Amazon Web Services (AWS) can be a great way to scale and distribute your app to a global audience. In this blog post, we'll walk through the process of deploying an Angular application to AWS S3 with CloudFront and Route53 using Terraform.

Things we will need! 

Before getting started, it's important to make sure that you have the following prerequisites:

  • An AWS account
  • A running Angular application (or you can use ng new <APP NAME> to get you started)
  • Terraform installed on your local machine
  • AWS CLI installed on your local machine
  • AWS Access and Secret key
  • Basic knowledge of Angular and AWS services

Create a CloudFront Distribution

You should have already created the S3 bucket in the previous post so, the next step is to create a CloudFront distribution to handle the distribution of your app's files to users. CloudFront is a content delivery network (CDN) that can be used to distribute your app's files to a global audience.

resource "aws_cloudfront_distribution" "distribution_name" {
  origin {
    domain_name = aws_s3_bucket.bucket_name.bucket
    origin_id   = "S3-${aws_s3_bucket.bucket_name.bucket}"
  }
  enabled = true
  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "S3-${aws_s3_bucket.bucket_name.bucket}"
    forwarded_values {
      query_string = false
    }
  }
}


 

Adding a DNS with Route53

The next step in deploying your Angular application to AWS is to create a Route53 record to handle the routing of traffic to your app. Route53 is a scalable and highly available Domain Name System (DNS) service that can be used to route traffic to your app.

resource "aws_route53_record" "route53_record" {
  zone_id = "your-route53-zone-id"
  name    = "your-domain-name"
  type    = "A"
  alias {
    name                   = aws_cloudfront_distribution.distribution_name.domain_name
    zone_id                = aws_cloudfront_distribution.distribution_name.hosted_zone_id
    evaluate_target_health = true
  }
}

Build and Deploy the Application

Now that all the necessary resources are created for our Angular application, we can build and deploy the application on the S3 bucket.

To build the application you can use the command:

npm run build --prod

Then you can use the AWS CLI command to sync the built files into your S3 Bucket:

aws s3 sync build/ s3://your-bucket-name/

Deploy the Terraform Configuration

Once the application is built, you can deploy the Terraform configuration to create and configure the necessary resources for your Angular application on AWS.

$ terraform init

$ terraform apply

The terraform init command is used to initialise the Terraform configuration, while the terraform apply command is used to create and configure the resources defined in the configuration.

Verify the Deployment

To verify that your Angular application has been deployed correctly, you can visit the URL of your application in a web browser. This URL can be found in the AWS S3 console or in the Route53 record created earlier.

You can also check the CloudFront distribution status by visiting the CloudFront Management Console, ensuring the status is "Deployed".

Maintenance and Updating

In order to update the Angular application, you will have to rebuild the application and sync the new build with the S3 bucket. Then, you will have to run the terraform apply command to update the Cloudfront distribution.

It's also important to keep an eye on the costs associated with your AWS resources. You can use the AWS Cost Explorer to view and manage your AWS costs.

Wrap Up!

As you can see, deploying an Angular application to AWS S3 with CloudFront and Route53 can be a great way to scale and distribute your app to a global audience. By using Terraform, you can automate the process of creating and configuring the necessary resources, making the deployment process faster and more efficient. Remember to keep an eye on costs and always verify the application is deployed correctly.