...

What We Think

Blog

Keep up with the latest in technological advancements and business strategies, with thought leadership articles contributed by our staff.
TECH

December 3, 2025

Real-world Terraform Project Structure

I. Introduction.

Separating directories for different environments (Dev, Staging, Production) is a mandatory standard in real-world projects to ensure security and manageability.

The best approach is to use a modular model. We will refactor the previous EC2 creation code into a shared module (template), which the Dev and Prod environments will then call using different parameters.

II. Terraform Project Structure.

We will create a Terraform project with the following requirements:

  • Separate configurations for dev and prod environments.

  • Define shared variables for code reusability.

  • Store the State File in AWS S3.

Below is the new directory structure and the implementation steps:

The function of each folder is as follows:

setup-backend: Configuration for the Terraform backend.

modules: Stores reusable code modules.

environments: Contains environment-specific configurations.

File: setup-backend\main.tf

The function of this code is to bootstrap the foundational infrastructure required for a Terraform Remote Backend. This allows the State file to be stored securely on the Cloud instead of a local machine, which is essential for team collaboration.

# Configure the AWS Provider and set the region where resources will be created.
provider "aws" {
  region = "us-east-1"
}

# Create an S3 bucket to store the 'terraform.tfstate' file.
resource "aws_s3_bucket" "terraform_state" {
  bucket = "terraform-state-project-9999"
  lifecycle {
    prevent_destroy = true 
  }
}

# Enable versioning on the S3 bucket.
# This is crucial for state recovery. It allows you to revert to an older 
# state file if the current one gets corrupted or accidentally overwritten.
resource "aws_s3_bucket_versioning" "enabled" {
  bucket = aws_s3_bucket.terraform_state.id
  versioning_configuration {
    status = "Enabled"
  }
}

# Create a DynamoDB table to handle state locking.
# This prevents race conditions (e.g., two developers running 'apply' simultaneously).
resource "aws_dynamodb_table" "terraform_locks" {
  name         = "terraform-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

# Output the name of the S3 bucket for easy reference later.
output "bucket_name" {
  value = aws_s3_bucket.terraform_state.bucket
}

# Output the name of the DynamoDB table for easy reference later.
output "dynamodb_table_name" {
  value = aws_dynamodb_table.terraform_locks.name
}

 

File: modules\ec2_instance\main.tf

This code snippet creates a basic Web Server infrastructure on AWS. It consists of two main components: a "Virtual Firewall" (Security Group) and a "Virtual Server" (EC2 Instance) protected by that firewall.

resource "aws_security_group" "sg" {
  name        = "${var.env_name}-sg"
  description = "Security Group for ${var.env_name}"

  ingress {
   ...
  }
  
  egress {
   ...
  }
}

resource "aws_instance" "app_server" {
  ami             = var.ami_id
  instance_type   = var.instance_type
  security_groups = [aws_security_group.sg.name]

  tags = {
    Name        = "${var.env_name}-Web-Server"
    Environment = var.env_name
  }
}

 

File: environments\dev\main.tf

This code represents a complete Terraform configuration (Root Module) for the Development environment. It orchestrates various infrastructure components: from state file storage (Backend) to resource instantiation (Module), and finally displaying the results.

# TERRAFORM CONFIGURATION
terraform {
  # Define the providers required by this configuration.
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  # Configure Terraform to store the state file in an S3 bucket instead of locally.
  backend "s3" {
    bucket         = "terraform-state-project-9999" 
    key            = "dev/terraform.tfstate" 
    region         = "us-east-1"
    dynamodb_table = "terraform-locks" 
    encrypt        = true
  }
}

# Configure the AWS Provider to deploy resources into the US East 1 region.
provider "aws" {
  region = "us-east-1"
}

# Instantiate the 'web_server' module using code defined in a local directory.
# This promotes code reusability and cleaner project structure.
module "web_server" {
  # The relative path to the module source code.
  source = "../../modules/ec2_instance"
  # Pass input variables to the module to customize its behavior.
  env_name      = "dev"
  instance_type = "t3.micro"
  ami_id        = "ami-0fa3fe0fa7920f68e"
}

# Retrieve the public IP address from the module's outputs and display it.
output "server_ip" {
  value = module.web_server.public_ip
}

 

File: environments\prod\main.tf

This code represents a complete Terraform configuration (Root Module) for the Production environment. It orchestrates various infrastructure components: from state file storage (Backend) to resource instantiation (Module), and finally displaying the results.

# TERRAFORM CONFIGURATION
terraform {
  # Define the providers required by this configuration.
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  # Configure Terraform to store the state file in an S3 bucket instead of locally.
  backend "s3" {
    bucket         = "terraform-state-project-9999" 
    key            = "prod/terraform.tfstate" 
    region         = "us-east-1"
    dynamodb_table = "terraform-locks" 
    encrypt        = true
  }
}
# Configure the AWS Provider to deploy resources into the US East 1 region.
provider "aws" {
  region = "us-east-1"
}

# Instantiate the 'web_server' module using code defined in a local directory.
# This promotes code reusability and cleaner project structure.
module "web_server" {
  # The relative path to the module source code.
  source = "../../modules/ec2_instance"
  # Pass input variables to the module to customize its behavior.
  env_name      = "prod"
  instance_type = "t3.micro"
  ami_id        = "ami-0fa3fe0fa7920f68e"
}
# Retrieve the public IP address from the module's outputs and display it.
output "server_ip" {
  value = module.web_server.public_ip
}

 

Full source code: https://github.com/ivc-phampbt/terraform-project

III. Run the project to provision resources on the AWS Cloud.

  • Proceed to provision the Terraform backend resources.
taipham@Tais setup-backend % pwd
/Users/taipham/Desktop/projects/terraform/setup-backend
taipham@Tais setup-backend % terraform init
taipham@Tais setup-backend % terraform apply

 

  • Initializing the Development Environment (Proceed similarly for Production)
taipham@Tais dev % pwd  
/Users/taipham/Desktop/projects/terraform/environments/dev
taipham@Tais dev % terraform init
taipham@Tais dev % terraform apply

 

  • Result on AWS: Two EC2 instances were created for the production and development environments

  • Check the S3 bucket for the stored state file.

Development:

Production:

Conclusion

The project successfully transitioned to a professional IaC architecture by implementing the Module pattern and deploying a secure Remote Backend on AWS.

Key Achievements:

  • High Reusability: Infrastructure logic was encapsulated into Terraform Modules, ensuring consistency between Dev and Prod environments.

  • Safety & Collaboration: Implemented a Remote Backend using S3 (for state storage) and DynamoDB (for state locking), ensuring secure team collaboration.

  • Separation: The project structure clearly isolates Dev/Prod environments, confirming that each state file is independently managed and verified.

Whether you need scalable software solutions, expert IT outsourcing, or a long-term development partner, ISB Vietnam is here to deliver. Let’s build something great together—reach out to us today. Or click here to explore more ISB Vietnam's case studies.

[References]

https://developer.hashicorp.com/terraform
https://www.sudeepa.com/?p=382 [Image link]

View More
TECH

December 3, 2025

What is Terraform?

I. What is Terraform?

Terraform is an Infrastructure as Code (IaC) tool by HashiCorp that allows you to manage infrastructure (such as virtual machines, networks, databases, etc.) by writing code instead of performing manual operations. You write configuration files—typically in HashiCorp Configuration Language (HCL)—and run Terraform commands to create, modify, or destroy resources across various cloud platforms (like AWS, Azure, GCP).

II. Basic Terraform Workflow.

The core Terraform workflow typically consists of 4 main steps:

1. Write Configuration.

  • You create files with the .tf extension (e.g., main.tf, variables.tf).
  • In these files, you declare Providers (service providers, e.g., aws, azurerm) and Resources (infrastructure objects, e.g., EC2 instances, VPC networks).

2. Initialization.

  • This command is run the first time within a Terraform project directory.
  • It downloads the necessary Providers so that Terraform can communicate with your cloud services.

3. Planning.

  • This command reads the configuration files (.tf) and compares the desired state with the actual state of the current infrastructure (which is stored in the State File – terraform.tfstate).
  • It displays a detailed execution plan showing exactly what will be added, changed, or destroyed.

4. Applying.

  • After you review and approve the plan, this command executes the changes on the cloud provider's actual infrastructure.
  • It updates the State File to reflect the new state of the infrastructure.

III. Basic Terraform Commands.

Command Purpose
terraform init Initialize the working directory, download Providers.
terraform validate Check the syntax of configuration files.
terraform plan Show the plan of changes (what will happen).
terraform apply Execute the plan, create/update resources.
terraform destroy Destroy all resources managed by Terraform.
terraform fmt Reformat configuration files to HCL standards.

 

IV. State File.

 

The State File is a critically important component of Terraform.

  • It records the current state of the infrastructure that Terraform has created and is managing.
  • It enables Terraform to determine the difference between your configuration (.tf files) and the actual state of resources in the cloud.
  • In a team environment, this file is typically stored remotely (e.g., in an S3 Bucket or Terraform Cloud) to avoid conflicts and ensure consistency.

V. Simple Code Example: Creating an EC2 Instance on AWS.

File: main.tf

provider "aws" {
 region = "us-east-1"
}

resource "aws_instance" "hello" {
 ami           = "ami-0fa3fe0fa7920f68e"
 instance_type = "t3.micro"
 tags = {
   Name = "terraform1"
 }
}

 

- Run the terraform init command to initialize the working directory and download providers.

taipham@Tais terraform % terraform init
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/aws v6.23.0...
- Installed hashicorp/aws v6.23.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

 

- Run the terraform apply -auto-approve command to provision an EC2 instance named 'terraform1'.

taipham@Tais terraform % terraform apply -auto-approve
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following
symbols:
  + create
Terraform will perform the following actions:
  # aws_instance.hello will be created
  + resource "aws_instance" "hello" {
      + ami                                  = "ami-0fa3fe0fa7920f68e"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = (known after apply)
      + availability_zone                    = (known after apply)
      + disable_api_stop                     = (known after apply)
      + disable_api_termination              = (known after apply)
      + ebs_optimized                        = (known after apply)
      + enable_primary_ipv6                  = (known after apply)
      + force_destroy                        = false
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + host_resource_group_arn              = (known after apply)
      + iam_instance_profile                 = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = (known after apply)
      + instance_lifecycle                   = (known after apply)
      + instance_state                       = (known after apply)
      + instance_type                        = "t3.micro"
      + ipv6_address_count                   = (known after apply)
      + ipv6_addresses                       = (known after apply)
      + key_name                             = (known after apply)
      + monitoring                           = (known after apply)
      + outpost_arn                          = (known after apply)
      + password_data                        = (known after apply)
      + placement_group                      = (known after apply)
      + placement_group_id                   = (known after apply)
      + placement_partition_number           = (known after apply)
      + primary_network_interface_id         = (known after apply)
      + private_dns                          = (known after apply)
      + private_ip                           = (known after apply)
      + public_dns                           = (known after apply)
      + public_ip                            = (known after apply)
      + region                               = "us-east-1"
      + secondary_private_ips                = (known after apply)
      + security_groups                      = (known after apply)
      + source_dest_check                    = true
      + spot_instance_request_id             = (known after apply)
      + subnet_id                            = (known after apply)
      + tags                                 = {
          + "Name" = "terraform1"
        }
      + tags_all                             = {
          + "Name" = "terraform1"
        }
      + tenancy                              = (known after apply)
      + user_data_base64                     = (known after apply)
      + user_data_replace_on_change          = false
      + vpc_security_group_ids               = (known after apply)
      + capacity_reservation_specification (known after apply)
      + cpu_options (known after apply)
      + ebs_block_device (known after apply)
      + enclave_options (known after apply)
      + ephemeral_block_device (known after apply)
      + instance_market_options (known after apply)
      + maintenance_options (known after apply)
      + metadata_options (known after apply)
      + network_interface (known after apply)
      + primary_network_interface (known after apply)
      + private_dns_name_options (known after apply)
      + root_block_device (known after apply)
    }
Plan: 1 to add, 0 to change, 0 to destroy.
aws_instance.hello: Creating...
aws_instance.hello: Still creating... [00m10s elapsed]
aws_instance.hello: Creation complete after 18s [id=i-09233298b048820fa]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
taipham@Tais terraform % 

 

- Verify on AWS that an instance named 'terraform1' has been successfully launched.


 

- Delete the resources using the terraform destroy command.

taipham@Tais-MacBook-Pro terraform % terraform destroy
aws_instance.hello: Refreshing state... [id=i-09233298b048820fa]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following
symbols:
  - destroy
Terraform will perform the following actions:
  # aws_instance.hello will be destroyed
  - resource "aws_instance" "hello" {
      - ami                                  = "ami-0fa3fe0fa7920f68e" -> null
      - arn                                  = "arn:aws:ec2:us-east-1:075134876480:instance/i-09233298b048820fa" -> null
      - associate_public_ip_address          = true -> null
      - availability_zone                    = "us-east-1f" -> null
      - disable_api_stop                     = false -> null
      - disable_api_termination              = false -> null
      - ebs_optimized                        = false -> null
      - force_destroy                        = false -> null
      - get_password_data                    = false -> null
      - hibernation                          = false -> null
      - id                                   = "i-09233298b048820fa" -> null
      - instance_initiated_shutdown_behavior = "stop" -> null
      - instance_state                       = "running" -> null
      - instance_type                        = "t3.micro" -> null
      - ipv6_address_count                   = 0 -> null
      - ipv6_addresses                       = [] -> null
      - monitoring                           = false -> null
      - placement_partition_number           = 0 -> null
      - primary_network_interface_id         = "eni-0a9eef084942a77ad" -> null
      - private_dns                          = "ip-172-31-64-53.ec2.internal" -> null
      - private_ip                           = "172.31.64.53" -> null
      - public_dns                           = "ec2-13-220-15-145.compute-1.amazonaws.com" -> null
      - public_ip                            = "13.220.15.145" -> null
      - region                               = "us-east-1" -> null
      - secondary_private_ips                = [] -> null
      - security_groups                      = [
          - "default",
        ] -> null
      - source_dest_check                    = true -> null
      - subnet_id                            = "subnet-0cc7a1fa7009a4c48" -> null
      - tags                                 = {
          - "Name" = "terraform1"
        } -> null
      - tags_all                             = {
          - "Name" = "terraform1"
        } -> null
      - tenancy                              = "default" -> null
      - user_data_replace_on_change          = false -> null
      - vpc_security_group_ids               = [
          - "sg-067709313e763db77",
        ] -> null
        # (9 unchanged attributes hidden)
      - capacity_reservation_specification {
          - capacity_reservation_preference = "open" -> null
        }
      - cpu_options {
          - core_count       = 1 -> null
          - threads_per_core = 2 -> null
            # (1 unchanged attribute hidden)
        }
      - credit_specification {
          - cpu_credits = "unlimited" -> null
        }
      - enclave_options {
          - enabled = false -> null
        }
      - maintenance_options {
          - auto_recovery = "default" -> null
        }
      - metadata_options {
          - http_endpoint               = "enabled" -> null
          - http_protocol_ipv6          = "disabled" -> null
          - http_put_response_hop_limit = 2 -> null
          - http_tokens                 = "required" -> null
          - instance_metadata_tags      = "disabled" -> null
        }
      - primary_network_interface {
          - delete_on_termination = true -> null
          - network_interface_id  = "eni-0a9eef084942a77ad" -> null
        }
      - private_dns_name_options {
          - enable_resource_name_dns_a_record    = false -> null
          - enable_resource_name_dns_aaaa_record = false -> null
          - hostname_type                        = "ip-name" -> null
        }
      - root_block_device {
          - delete_on_termination = true -> null
          - device_name           = "/dev/xvda" -> null
          - encrypted             = false -> null
          - iops                  = 3000 -> null
          - tags                  = {} -> null
          - tags_all              = {} -> null
          - throughput            = 125 -> null
          - volume_id             = "vol-0cf12ff2a72cff8c0" -> null
          - volume_size           = 8 -> null
          - volume_type           = "gp3" -> null
            # (1 unchanged attribute hidden)
        }
    }
Plan: 0 to add, 0 to change, 1 to destroy.
Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.
  Enter a value: yes
aws_instance.hello: Destroying... [id=i-09233298b048820fa]
aws_instance.hello: Still destroying... [id=i-09233298b048820fa, 00m10s elapsed]
aws_instance.hello: Still destroying... [id=i-09233298b048820fa, 00m20s elapsed]
aws_instance.hello: Still destroying... [id=i-09233298b048820fa, 00m30s elapsed]
aws_instance.hello: Destruction complete after 32s
Destroy complete! Resources: 1 destroyed.

 

- The instance named 'terraform1' has been terminated.

Conclusion

In summary, Terraform is a powerful Infrastructure as Code (IaC) tool by HashiCorp that enables users to manage multi-cloud infrastructure (such as AWS, Azure, and GCP) through declarative HCL code rather than manual operations. Its core workflow revolves around four main steps: Writing Configuration, Initialization (terraform init), Planning (terraform plan), and Applying (terraform apply).

A critical component of this process is the State File, which records the current status of the infrastructure to track differences between the code and the actual environment, ensuring consistency especially in team settings. As demonstrated in the practical AWS example, Terraform allows for the complete lifecycle management of resources—from provisioning an EC2 instance with terraform apply to removing it with terraform destroy—ensuring automation and precision.

Whether you need scalable software solutions, expert IT outsourcing, or a long-term development partner, ISB Vietnam is here to deliver. Let’s build something great together—reach out to us today. Or click here to explore more ISB Vietnam's case studies.

[References]

https://developer.hashicorp.com/terraform
https://www.sudeepa.com/?p=382 [Image link]

View More
TECH

December 3, 2025

Introducing Vite.js: The Fast Frontend Build Tool

Building modern web apps can be slow and frustrating if your tools are not efficient. Vite.js is a modern frontend build tool that makes development faster and smoother, helping developers focus on writing code instead of waiting for builds.

What is Vite.js?

Vite.js is a next-generation frontend build tool created by Evan You, the developer behind Vue.js. Unlike traditional bundlers, Vite serves source files on-demand using native ES modules, which means:

  • Almost instant server start
  • Fast hot module replacement (HMR) during development
  • Optimized production builds using Rollup

Vite works with any frontend framework, including Vue, React, Svelte, or even vanilla JavaScript.

Key Benefits

  1. Super fast development server – no long waits.

  2. Instant hot module replacement – changes appear immediately in the browser.

  3. Optimized production build – small and efficient bundles.

  4. Framework agnostic – works with Vue, React, Svelte, or vanilla JS.

Getting Started (Example with Vue 3)

Here’s a quick example of using Vite with Vue 3:

npm create vite@latest my-vue-app
cd my-vue-app
npm install
npm run dev

And a simple Vue component:

<template>
   <h1>{{ message }}</h1>
</template>

<script setup>
import { ref } from 'vue'
const message = ref('Hello from Vite + Vue 3!')
</script>

With HMR, any changes to this component are instantly reflected in the browser.

Why Choose Vite?

Traditional bundlers like Webpack can become slow as projects grow. Vite solves this by pre-bundling dependencies and using native ESM, making it ideal for modern frontend development.

If you want fast iteration, easy setup, and optimized builds, Vite is a great choice for any frontend project.

Conclusion

Vite.js simplifies frontend development with blazing-fast startup, instant HMR, and optimized production builds. Its speed and efficiency make it a powerful tool for modern web development.

If you're seeking a reliable, long-term partner who values collaboration and shared growth, feel free to reach out to us here: Contact ISB Vietnam

[References]

View More
TECH

December 3, 2025

SPRING BOOT SECURITY: SECURING WEB APPLICATIONS

In modern web application development, security is essential. Spring Boot integrates seamlessly with Spring Security, providing mechanisms for authentication, authorization, and endpoint protection out-of-the-box. This section explains Spring Boot Security, how to configure it, practical examples, and its benefits.

I. What is Spring Boot Security?

Spring Boot Security is a powerful framework that helps you:

  • Authenticate users (Authentication)
  • Control access to resources (Authorization)
  • Protect your application against common attacks such as CSRF, XSS, and session fixation
  • Support OAuth2, JWT, Basic Auth, and Form Login

By adding the spring-boot-starter-security dependency, Spring Boot Security can secure your application with minimal configuration.

II. How Does Spring Boot Security Work?

Spring Boot Security works using Filter Chains and the SecurityContext:

  • Filter Chain: Intercepts all incoming requests and applies security rules.
  • AuthenticationManager: Validates user credentials.
  • Authorization: Determines if the authenticated user has permission to access a resource.
  • PasswordEncoder: Hashes passwords for secure storage.

By default, if you add spring-boot-starter-security, all endpoints require login with a default username: user and a generated password.

III. How to Use Spring Boot Security

1. Add Dependency

Maven:

<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-security</artifactId>

</dependency>

Gradle:

implementation 'org.springframework.boot:spring-boot-starter-security

 

2. Configure Security

You can customize security using SecurityFilterChain (Spring Boot 2.7+ / 3.x):

@Configuration

@EnableWebSecuritypublic class SecurityConfig {    

   @Bean   

   public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {       

         http           

               .csrf().disable() 

               .authorizeHttpRequests(auth -> auth               

                             .requestMatchers("/admin/**")

                            .hasRole("ADMIN")              

                             .requestMatchers("/user/**")

                            .hasAnyRole("USER", "ADMIN")               

                            .anyRequest().permitAll())    

               .formLogin() 

               .and()           

               .httpBasic();       

        return http.build();   

   }    

   @Bean

   public PasswordEncoder passwordEncoder() {       

         return new BCryptPasswordEncoder();   

   }

}

 

  • /admin/** is accessible only by admins
  • /user/** is accessible by both users and admins
  • All other endpoints are public
  • Supports form login and HTTP Basic authentication

3. Im-Memory Authentication Example

@Bean

public UserDetailsService users() {   

    UserDetails user = User.builder()       

                            .username("user")       

                            .password(passwordEncoder().encode("password"))       

                            .roles("USER")       

                            .build();        

    UserDetails admin = User.builder()       

                            .username("admin")       

                            .password(passwordEncoder().encode("admin123"))       

                            .roles("ADMIN")       

                            .build();        

     return new InMemoryUserDetailsManager(user, admin);

}

 

This is a quick way to test security without a database.

IV. Example of Spring Boot Security in Action

Protecting a REST API:

@RestController

@RequestMapping("/admin")

public class AdminController {        

    @GetMapping("/dashboard")   

    public String dashboard() {       

         return "Admin Dashboard";    

    }

}

 

When accessing /admin/dashboard:

  • If not logged in → redirected to login page
  • If logged in with a user without ADMIN role → 403Forbidden
  • If logged in as admin → access granted

V. Benefits of Spring Boot Security

  • Secure by default: Adding the dependency provides login and basic security.
  • Flexible authentication and authorization: Role-based, permission-based, JWT, OAuth2 support.
  • Protection against common attacks: CSRF, XSS, session fixation.
  • Highly customizable: Form login, REST API security, method-level security (@PreAuthorize).
  • Easy integration with DB or OAuth2 providers: JDBC, LDAP, Keycloak, Google, Facebook.

VI. Conclusion

Spring Boot Security allows you to secure web applications easily, with minimal configuration and robust features. It not only protects your endpoints but also scales for complex authentication and authorization needs, letting developers focus more on business logic rather than security boilerplate.

Whether you need scalable software solutions, expert IT outsourcing, or a long-term development partner, ISB Vietnam is here to deliver. Let’s build something great together—reach out to us today. Or click here to explore more ISB Vietnam's case studies.

[References]

https://docs.spring.io/spring-boot/docs/current/reference/html/security.html

https://docs.spring.io/spring-security/reference/index.html

https://www.baeldung.com/spring-boot-security

https://spring.io/guides/gs/securing-web/

https://www.baeldung.com/spring-security-jwt

View More
TECH

December 2, 2025

Build an App from SharePoint with Power Apps – Part 2

Welcome back to our series “From Zero to Digital Hero with Power Apps.”
In the previous post, you learned what Power Apps is and why it’s a game-changer for modern workplaces.
Now, it’s time to turn theory into action — we’ll build your very first app, using SharePoint as the data source.

https://isb-vietnam.net/blog/tech/what-is-power-apps-starting-your-digital-journey-from-zero-part-1/

1. Prepare Your Data in SharePoint

Before creating the app, we need a list that stores all our information.
Think of SharePoint lists like a smart Excel table — accessible online, collaborative, and ready to connect with Power Apps.
Example: Employee Requests list

How to create it:

  • Go to your SharePoint site (e.g., https://yourcompany.sharepoint.com/sites/team)

  • Click New → List → Blank list

  • Name it EmployeeRequests

  • Add columns:

    • Title – Request title (default column)

    • EmployeeName – Single line of text

    • Department – Choice: HR, IT, Finance, Sales

    • RequestDate – Date and Time

    • Status – Choice: Pending, Approved, Rejected

*Avoid spaces in column names (e.g., use EmployeeName instead of Employee Name) to prevent syntax issues in Power Apps.

2. Create the App Directly from SharePoint

Now comes the fun part — generating the app automatically!

  1. Open your EmployeeRequests list in SharePoint.

  2. On the top menu, select Integrate → Power Apps → Create an app.

  3. Give your app a name, for example: Employee Request App.

  4. Wait a few moments — Power Apps will build your app with three default screens:

    • Browse Screen – View all requests

    • Detail Screen – View request details

    • Edit Screen – Create or modify a request

Behind the scenes: Power Apps automatically reads your list structure and builds a connected interface in seconds — something that used to take hours of coding!

3. Customize Your App

Once the app opens in Power Apps Studio, it’s time to make it yours.

  • Change the title:
    Select the top label → Rename it to Employee Requests Management

  • Adjust the layout:
    Choose the gallery → Layout → Title and Subtitle

  • Show the right fields:

    • Title = ThisItem.EmployeeName

    • Subtitle = ThisItem.Status

*Keep your design simple. End users prefer fewer clicks and clear labels — not fancy graphics.

4. Add More Data Connections (Optional)

Want to expand your app’s power?
Connect additional data sources like Microsoft Teams, Outlook, or another SharePoint list (e.g., Departments) for lookups.

How to do it:

Go to Data → Add data → SharePoint → Select your site → Choose the list.

5. Test and Share Your App

Click Play ▶️ to test your app.

Try these actions:

  • Create a new request

  • Update the status (Pending → Approved)

  • Delete a record

When ready:

  1. Go to File → Save → Publish

  2. Return to SharePoint

  3. You’ll find your app under Power Apps → Apps

Share your success: Click Share to invite colleagues, or embed the app directly in your SharePoint page or Teams channel for daily use
 

 

Conclusion – You’ve Built Your First Power App!

Congratulations — you’ve just created a working app straight from a SharePoint list without writing a single line of code!

Whether you need scalable software solutions, expert IT outsourcing, or a long-term development partner, ISB Vietnam is here to deliver. Let’s build something great together—reach out to us today. Or click here to explore more ISB Vietnam's case studies.

View More
TECH

December 2, 2025

The Ultimate Guide to Detecting TURN Server Usage in WebRTC

How to Detect TURN Server Usage in WebRTC

In WebRTC, connecting two peers involves finding the best path using Interactive Connectivity Establishment (ICE). This path can be direct (peer-to-peer) or relayed via a TURN server. Knowing which path your connection uses is important for monitoring performance and managing costs.

Your connection type—direct or relayed—is determined by the selected ICE candidate.

The Three Types of ICE Candidates

ICE candidates are the network addresses your browser discovers to reach a remote peer:

Candidate Type Description Connection Path
host Direct local IP (LAN) Direct (Local Network)
srflx Public IP discovered via STUN Direct (Internet P2P)
relay Routed through a TURN server Relayed (TURN Server)

Tip: If the selected candidate type is relay, your connection is definitely using a TURN server

Step 1: Listen for ICE Candidates

You can track all discovered candidates by listening for the icecandidate event on your RTCPeerConnection:

peerConnection.addEventListener("icecandidate", event => {
       if (event.candidate) {
             console.log("ICE candidate:", event.candidate.candidate);
           // Look for "typ relay" to detect TURN usage
       }
});

Step 2: Check the Selected Candidate Pair Using getStats()

The most reliable method is using the getStats() API. This reports the candidate pair that has been selected and successfully connected.

async function checkTurnUsage(peerConnection) {
       const stats = await peerConnection.getStats();

       stats.forEach(report => {
             if (report.type === "candidate-pair" && report.state === "succeeded" && report.selected) {
                   const local = stats.get(report.localCandidateId);
                   const remote = stats.get(report.remoteCandidateId);

                   if (local?.candidateType === "relay" || remote?.candidateType === "relay") {
                         console.log("Connection is using TURN (relay).");
                   } else {
                         console.log("Connection is direct (host/srflx).");
                   }
             }
       });
}

Step 3: Continuously Monitor Path Changes

WebRTC connections can switch ICE candidates if network conditions change. To monitor dynamically, listen for relevant events:

// When the selected candidate pair changes
peerConnection.addEventListener("icecandidatepairchange", () => {
       checkTurnUsage(peerConnection);
});

// When the connection becomes stable
peerConnection.addEventListener("iceconnectionstatechange", () => {
       if (peerConnection.iceConnectionState === "connected" ||
            peerConnection.iceConnectionState === "completed") {
             checkTurnUsage(peerConnection);
       }
});

Summary Checklist

Action Purpose Key Indicator
Check ICE Candidate Type Identify potential paths host → local / srflx → direct P2P / relay → TURN
Use getStats() Confirm selected pair Look for candidateType: "relay"
Monitor Events Track dynamic changes icecandidatepairchange or iceconnectionstatechange

 

Conclusion

Detecting TURN server usage is crucial for optimizing WebRTC performance and controlling costs. By understanding host, srflx, and relay candidates, using getStats() to verify the selected pair, and monitoring events for changes, developers can ensure reliable, real-time connectivity. This approach helps deliver smooth, high-quality WebRTC experiences while keeping infrastructure usage efficient.

Ready to get started?

Contact IVC for a free consultation and discover how we can help your business grow online.

Contact IVC for a Free Consultation
View More
TECH

December 2, 2025

Secure KVS WebRTC with AWS STS Temporary Credentials

Use AWS STS to Get Temporary Credentials for KVS WebRTC

In modern web apps, hardcoding long-term AWS credentials is risky, especially for real-time services like Kinesis Video Streams (KVS) WebRTC. The safe way is to use temporary credentials generated by AWS Security Token Service (STS). This guide explains how it works and how to securely connect to AWS KVS.

What is AWS STS?

AWS STS issues temporary security credentials so your app can access AWS resources without storing long-term keys.

Feature Description
Duration Short-lived, from a few minutes up to a few hours
Based on An existing identity (IAM user, IAM role, SAML, OIDC, etc.)
Use Case Temporary access for mobile/web apps, third-party access, or high-security scenarios

Tip: Using temporary credentials helps reduce security risks and adhere to the principle of least privilege

How Authentication Works

Here’s the typical flow for getting temporary credentials for KVS:

  1. User Login: The user opens your web app and logs in.
  2. JWT Token: After login, the web app receives a JWT token from Cognito User Pool.
  3. Identity Exchange: The app sends the JWT to Cognito Identity Pool, which internally calls STS AssumeRoleWithWebIdentity.
  4. Temporary Credentials: STS returns credentials: AccessKeyId, SecretAccessKey, SessionToken.

KVS Access: The web app uses these credentials to call Kinesis Video Streams WebRTC APIs

Sequence: Get Credentials and Connect

Here’s a step-by-step sequence of communication

Step Who Action Recipient Note
1 User Start LIVE request Web backend Initiates streaming
2 Web Request Credential AWS Cognito Starts auth and token exchange
3 AWS Cognito Request temp credentials AWS STS STS generates short-lived credentials
4 Web Return STS credentials User frontend Frontend receives temporary keys
5 User Connect WebRTC using STS AWS KVS Secure connection established
6 Connection Connected .. Temporary, secure streaming active

 

Handling Expired Credentials

  • STS credentials are short-lived (default: 1 hour).
  • If they expire, the web app cannot continue streaming.

Make sure your app refreshes credentials automatically when needed

Quick Checklist

  • Get JWT token after user login
  • Exchange JWT with Cognito Identity Pool → STS generates temporary credentials
  • Use credentials to connect WebRTC → Secure, temporary connection to KVS
  • Monitor expiration → Refresh credentials before they expire

Why This Matters

Using Cognito + STS is the secure, recommended approach:

  • No long-term keys in the app
  • Access follows least privilege principle
  • Seamless, secure real-time streaming

It works for Vue, React, or any web client that needs temporary access to AWS KVS WebRTC

Conclusion

By leveraging AWS STS with Cognito, your web app can securely access Kinesis Video Streams without hardcoding credentials. Following this flow:

  1. User logs in → JWT obtained
  2. JWT exchanged → Temporary credentials generated

Connect using temporary credentials → Secure streaming

Ready to get started?

Contact IVC for a free consultation and discover how we can help your business grow online.

Contact IVC for a Free Consultation
View More
TECH

December 2, 2025

Modernize Your WPF App with MahApps.Metro: The Ultimate Guide

Windows Presentation Foundation (WPF) remains a powerful UI framework, but let’s face it: its default styling looks outdated for modern standards. Creating a polished, Windows 11-style desktop app often requires substantial time spent building custom themes, controls, and animations from scratch.

This is where MahApps.Metro shines.

MahApps.Metro is an open-source library that transforms standard WPF applications into clean, modern, and stylish interfaces inspired by the Windows Metro/Fluent design language. With minimal setup and well-designed components, it has become one of the most popular UI frameworks for .NET developers.

In this article, we’ll explore what MahApps.Metro offers, why it’s useful, and how to integrate it into your next WPF project.

What Is MahApps.Metro?

MahApps.Metro is a comprehensive UI toolkit for WPF that overrides the default look and feel of your application. It provides:

  • Modern Aesthetics: Metro/Fluent-inspired themes out of the box.

  • Customization: Fully customizable color palettes and accents.

  • Rich Controls: Ready-made components such as dialogs, toggle switches, and tiles.

  • Visual Flair: Animated transitions and ripple effects.

  • Compatibility: Full support for .NET Framework, .NET Core, and .NET 5+.

Unlike building custom UserControls yourself, utilizing this library ensures a consistent design language across your entire application with minimal effort.

Why Use MahApps.Metro?

Modern, Professional Look

WPF’s default controls (like the standard gray button) look like they belong in Windows 98. MahApps.Metro instantly upgrades your application’s appearance to match modern OS standards.

Easy to Customize

Themes, accents, and color palettes can be changed dynamically at runtime. This makes implementation of Dark Mode and Light Mode seamless.

Rich Set of Controls

The library includes polished components that are missing from standard WPF, such as:

  • MetroWindow (Custom chrome window)

  • Flyouts

  • Dialogs

  • ToggleSwitch

  • RangeSlider

  • HamburgerMenu

Active and Mature Project

The library has been around for years and continues to receive updates, community support, and compatibility improvements. [External Link: Visit the official MahApps.Metro GitHub]

Installing MahApps.Metro

You can easily install the library via NuGet Package Manager.

Using Package Manager Console:

PowerShell
Install-Package MahApps.Metro

Using .NET CLI:

Bash
dotnet add package MahApps.Metro

Converting Your Window to MetroWindow

To enable the styling, you need to replace the default WPF <Window> with <controls:MetroWindow>.

Before:

XML
<Window x:Class="SampleApp.MainWindow" ...>

After:

  1. Add the namespace.

  2. Change the root element.

XML
<controls:MetroWindow
    x:Class="SampleApp.MainWindow"
    xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="My Modern App" Height="450" Width="800">
    
    <Grid>
        </Grid>
    
</controls:MetroWindow>

Visual Comparison: Below is the difference between a standard WPF window and the modernized look you get immediately after implementing MahApps.Metro.

 

Applying a Theme and Accent

MahApps.Metro uses a ThemeManager to control global settings. You can set this in your App.xaml.cs or change it dynamically.

Set Dark Theme (Blue Accent):

C#
using ControlzEx.Theming;

// Inside your constructor or event handler
ThemeManager.Current.ChangeTheme(this, "Dark.Blue");

Set Light Theme (Steel Accent):

C#
ThemeManager.Current.ChangeTheme(this, "Light.Steel");

This flexibility is perfect for applications that need to respect user system preferences.

Exploring Key Controls

Flyouts

Flyouts are slide-out panels often used for menus, settings, or notifications. They don't block the UI like modal dialogs.

XML
<controls:MetroWindow.Flyouts>
    <controls:FlyoutsControl>
        <controls:Flyout Header="Settings" Position="Right" Width="300">
            <StackPanel>
                <TextBlock Text="Application Settings" Style="{StaticResource MetroTitleTextBlock}"/>
                </StackPanel>
        </controls:Flyout>
    </controls:FlyoutsControl>
</controls:MetroWindow.Flyouts>

Dialogs

Forget the ugly MessageBox.Show(). MahApps provides asynchronous, styled dialogs that overlay the window content nicely.

C#
using MahApps.Metro.Controls.Dialogs;

// Call this from your MetroWindow code-behind or ViewModel
await this.ShowMessageAsync("Success", "This is a modern MahApps dialog!");

Dialog Example: Here is how a clean, dark-themed dialog looks within the application:

 

ToggleSwitch

A replacement for the CheckBox, offering a modern mobile-style switch.

XML
<controls:ToggleSwitch Header="Enable Dark Mode" IsOn="True"/>

Implementing HamburgerMenu Navigation

One of the standout features is the HamburgerMenu, ideal for modern navigation-based apps (similar to UWP or Windows Settings).

XML
<controls:HamburgerMenu x:Name="HamburgerMenuControl"
                        HamburgerWidth="48"
                        ItemsSource="{Binding MenuItems}"
                        ItemTemplate="{StaticResource MenuItemTemplate}">
    
    <controls:HamburgerMenu.Content>
        <Frame x:Name="ContentFrame" NavigationUIVisibility="Hidden"/>
    </controls:HamburgerMenu.Content>
    
</controls:HamburgerMenu>

It gives your app a professional navigation experience with very little configuration.

Best Practices

To get the most out of MahApps.Metro:

  1. Consistent Resources: Use the defined DynamicResource brushes (like MahApps.Brushes.Accent) so your controls update automatically when the theme changes.

  2. Combine with MVVM: Keep your UI logic separate. MahApps works great with frameworks like Prism or MVVM Community Toolkit.

  3. Integration: It works well alongside MaterialDesignInXAML if you want a hybrid look.

Conclusion

MahApps.Metro is one of the most developer-friendly libraries available for WPF. It helps you modernize your application instantly, improve user experience, and reduce UI development time.

Whether you're building a new tool or modernizing a legacy enterprise app, MahApps.Metro is an excellent choice for creating a polished interface with minimal effort.

References

Ready to get started?

Contact IVC for a free consultation and discover how we can help your business grow online.

Contact IVC for a Free Consultation

View More
TECH

December 2, 2025

The Ultimate Guide to Windows Steps Recorder (PSR)

Step Recorder, also called Problem Steps Recorder (PSR), is a free tool built into Windows. It helps you capture actions, such as clicks, key presses, and screenshots. As a result, it is easier to show errors or explain steps to others. In addition, it does not require extra software.

What Step Recorder Does

Instead of recording a video, PSR creates a .zip file with a report (.mht or .html). For example, it includes:

  • Text Descriptions of each action (e.g., “User left-clicked the ‘New’ button in Microsoft Word.”)
  • Screenshots for each step, with the clicked area highlighted in green
  • System messages that appear during the process

Strengths

  • Built-in tool - No installation required
  • Ease of Use - The interface is intuitive with only 3 buttons (Start, Stop, Pause)
  • Accurate step-by-step logging of clicks, key presses, and errors
  • Small file size compared to full video recordings

Weaknesses

  • Not a video — does not capture mouse movement or animations
  • May become unresponsive in rare cases during long or complex recordings

How to Open Step Recorder

You can open Step Recorder in two ways:

1. Using Run

  • Press the Windows + R
  • Type psr
  • Click OK button.

2. Using Start Menu

  • Open Start
  • Search for “Steps Recorder”
  • Click to launch

How to Record Steps

1. Start Record

Click Start Record on the main interface.

2. Record

Reproduce the error or go through the workflow you want to document.

3. Stop Record

Click Stop Record when finished.

You will be prompted to save the recording as a .zip file.

Conclusion

Step Recorder is a simple Windows tool. In addition, it turns your actions into a clear report with text and screenshots. As a result, it helps document workflows and share problems with support teams. Overall, it is fast, easy to use, and does not require extra software. Therefore, it is an essential tool for troubleshooting and workflow documentation.

References:

https://support.microsoft.com/en-us/windows/steps-recorder-deprecation-a64888d7-8482-4965-8ce3-25fb004e975f
https://www.ninjaone.com/blog/enable-or-disable-steps-recorder-in-windows/#:~:text=Windows Steps Recorder%2C otherwise known,including screenshots and text descriptions.

Ready to get started?

Contact IVC for a free consultation and discover how we can help your business grow online.

Contact IVC for a Free Consultation
View More
TECH

December 2, 2025

Simulating Serial Port Devices When You Don’t Have Real Hardware

Your app connects to devices (ECS777, Glory300, etc.) via a serial port, but you don’t have the real hardware and still want to test and improve your code. Here’s a simple solution.

The architecture looks like this:

##################################### Linux os ############################################
###   [your app] <---->[/dev/ttychan] <====> [/dev/ttychansim] <---->  [dummyData app]  ###
##################################### Linux os ############################################

How to implement

Socat can create two bidirectional byte streams and link them together, allowing data to flow between /dev/ttychan and /dev/ttychansim. Currently, Socat is available in the package repository and you can install it using the command below

Strengths

  • Simple and easy to set up test data: Socat can connect almost anything to anything (TCP, UDP, UNIX sockets, PTY/TTY/Serial Ports, SSL, etc.). This makes it easy to simulate devices using Serial/COM ports and send data just like a real device
  • Supports autotesting: Since it is a command-line tool, Socat is ideal for automation (CI/CD pipelines, Bash/Python scripts). It can run in the background and create virtual port pairs for testing without manual work.

Weaknesses

  • Installing and using it through the command line can be difficult
  • Test data is fixed, so you need to create many different cases manually.Socat only forwards data. It cannot automatically respond with custom rules (e.g., “if command A is received, reply B after 500ms”). You need extra scripts (Python, Perl, etc.) for that

Installation

    # apt install socat
    # socat -V
        socat by Gerhard Rieger and contributors - see www.dest-unreach.org
        socat version 1.8.0.0 on 08 Apr 2024 14:50:22

A simple example

Suppose [yourApp] connects to a real hardware through /dev/ttychan, and you want to create a Python application [dummyData] to simulate the data sent to [yourApp].

Refer to the diagram below:

  • yourApp.c file
...
    const char *portname = "/dev/ttychan"; 
    int fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
    set_interface_attribs(fd, B9600, 'n');
...
...
    ser = serial.Serial(
        port='/dev/ttychansim',
        baudrate=9600
...
  • Create two bidirectional streams to allow data to flow between /dev/ttychan and /dev/ttychansim.
   sudo socat -d -d PTY,link=/dev/ttychan,raw,echo=0,b9600 PTY,link=/dev/ttychansim,raw,echo=0,b9600 &
  • Testing
Start dummyData

Start yourApp

Conclusion

The challenge of testing serial port applications without physical hardware is easily solved using Socat. By creating linked Virtual Serial Ports (PTY), Socat provides a simple, command-line solution that perfectly simulates real device communication. This immediately decouples development from hardware availability, making it an ideal method for autotesting and continuous integration pipelines. While complex device logic still requires custom scripting, the core barrier to flexible, automated testing is removed.

References:

https://man7.org/linux/man-pages/man1/socat.1.html

Ready to get started?

Contact IVC for a free consultation and discover how we can help your business grow online.

Contact IVC for a Free Consultation
View More
1 2 3 4 5 6 25
Let's explore a Partnership Opportunity

CONTACT US



At ISB Vietnam, we are always open to exploring new partnership opportunities.

If you're seeking a reliable, long-term partner who values collaboration and shared growth, we'd be happy to connect and discuss how we can work together.

Add the attachment *Up to 10MB