Why Critical Notification Failures Are So Hard to Reproduce
Go Concurrency with Generics: Speed Up Data Pipelines
Introduction
Efficient data processing is crucial for modern applications, especially when dealing with large datasets. Go’s concurrency model makes it an excellent choice for building high-performance data pipelines. With the introduction of generics in Go 1.18, we can now write type-safe concurrent functions that improve code reusability and maintainability.
In this blog, we will explore how Go concurrency and generics can be combined to process data pipelines efficiently.
Why Use Generics in Concurrency?
Prior to generics, go developers relied on interfaces (interface{}) to handle different data types. However, this led to type assertion overhead and reduced type safety. With generics, we can define functions that work with multiple data types while maintaining type safety, improving performance, and reducing boilerplate code.
Advantages of Using Generics in Concurrency
- Type Safety: Generics ensure that the correct data type is used, reducing runtime errors.
- Code Reusability: The same function can work with different types without requiring duplicate code.
- Performance Optimization: Eliminates type assertions, reducing execution time and memory overhead.
- Maintainability: Code becomes cleaner and easier to read, reducing debugging effort.
Drawbacks of Using Generics in Concurrency
- Learning Curve: Developers familiar with traditional Go may take time to adapt to generics.
- Increased Complexity: Generics can sometimes make the code more complex than necessary.
- Compile Time Overhead: Compilation may take longer due to type inference and checking.
How It Impacts Runtime Performance
Using generics in concurrency improves performance by reducing type assertions and ensuring direct usage of the correct types. However, improper use of concurrency (such as excessive goroutines or unbuffered channels) can lead to performance bottlenecks.
Implementing a Concurrent Data Pipeline with Generics
Let’s build a concurrent data pipeline using Go’s goroutines, channels, and generics.
Step 1: Define a Generic Worker Function
A worker function processes incoming data items concurrently. We use a generic type to handle different data types efficiently.
package main
import (
"fmt"
"sync"
"strings"
)
type ProcessFunc[T any] func(T) T
func worker[T any](data <-chan T, results chan<- T, process ProcessFunc[T], wg *sync.WaitGroup) {
defer wg.Done()
for item := range data {
results <- process(item)
}
}
Here, worker receives data through a channel, processes it using a generic function, and sends results to an output channel.
Step 2: Implement the Concurrent Pipeline
We now create a function to process a slice of data concurrently using multiple workers.
func processPipeline[T any](input []T, process ProcessFunc[T], numWorkers int) []T {
data := make(chan T, len(input))
results := make(chan T, len(input))
var wg sync.WaitGroup
// Start workers
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go worker(data, results, process, &wg)
}
// Send input data to workers
for _, item := range input {
data <- item
}
close(data)
// Wait for all workers to finish
wg.Wait()
close(results)
// Collect results
output := make([]T, 0, len(input))
for result := range results {
output = append(output, result)
}
return output
} This function:
- Distributes work across multiple goroutines.
- Uses channels for efficient communication.
- Ensures proper synchronization with sync.WaitGroup.
Step 3: Using the Generic Pipeline
Now, we can use this pipeline with different data types. Below is an example where we process integers and strings.
func main() {
// Example: Processing Integers
numbers := []int{1, 2, 3, 4, 5}
squaredNumbers := processPipeline(numbers, func(n int) int { return n * n }, 3)
fmt.Println("Squared Numbers:", squaredNumbers)
// Example: Processing Strings
words := []string{"go", "concurrency", "generics"}
upperWords := processPipeline(words, func(s string) string { return strings.ToUpper(s) }, 2)
fmt.Println("Uppercase Words:", upperWords)
} Performance Benefits
- Increased Throughput: Multiple goroutines process data simultaneously, reducing latency.
- Type Safety: Generics eliminate the need for type assertions and improve readability.
- Reusability: The same pipeline can handle various data types without duplication.
Conclusion
With Go generics and concurrency, we can build scalable, type-safe, and efficient data pipelines. By leveraging goroutines, channels, and generics, we can process large datasets with improved performance while keeping the code clean and reusable.
Try this approach in your next Go project and experience the power of modern concurrency!
OAuth2 & OpenID Connect Authentication for Cloud Run
Cloud Run is a serverless computing platform that allows you to deploy containerized applications with automatic scaling. However, securing these applications is crucial, especially when exposing them to the internet. OAuth2 and OpenID Connect (OIDC) provide robust authentication and authorization mechanisms to protect Cloud Run services. This blog post walks through implementing OAuth2 and OIDC authentication for a Cloud Run service.
What is OAuth2 and OpenID Connect?
- OAuth2: It is a standard authorization framework, giving third-party applications limited access to a web service without revealing the user’s credentials.
- OpenID Connect (OIDC): This is an identity layer on top of OAuth2 that offers authentication in addition to authorization.
You can use OIDC to authenticate your users to gain identity tokens proving the identity of users and is thus ideal for securing Cloud Run services.
Advantages of using OAuth2 and OpenID Connect (OIDC):
1. Enhanced Security
- Normalized Authentication: OAuth2 and OIDC standard industry mechanisms for authenticating and authorizing securely.
- Token-Based Security: They use access and ID tokens, reducing the need for storing sensitive user credentials.
- Single Sign-On (SSO): OIDC enables SSO, allowing users to log in once and access multiple services.
2. Seamless Integration with Identity Providers
- Works with Google Identity, Okta, Auth0, Azure AD, and other OAuth2/OIDC providers.
- Supports federated identity, allowing users to authenticate with social logins (Google, Facebook, GitHub, etc.).
3. Simplified Authentication for Cloud Run Services
- Identity-Aware Proxy (IAP): Google Cloud’s IAP can handle OAuth2/OIDC authentication without modifying your app.
- Automatic Token Verification: Cloud Run services can easily verify Google-issued ID tokens without additional libraries.
4. Scalability and Performance
- Stateless Authentication: No need to maintain session storage since OAuth2 tokens handle authentication.
- Efficient API Access: Using OAuth2, Cloud Run can securely call APIs (e.g., Google APIs) without requiring user credentials.
5. Role-Based Access Control (RBAC)
- OIDC allows you to extract user roles and permissions from identity providers, enabling fine-grained access control.
6. Reduced Development Overhead
- Google Cloud offers built-in support for OAuth2/OIDC, reducing the need for manual authentication handling.
- Managed services like Firebase Authentication or Cloud Identity Platform can be easily integrated.
7. Improved User Experience
- Enables secure and password-less authentication with providers like Google.
- Users can authenticate across multiple applications without re-entering credentials.
8. Compatibility with Service-to-Service Authentication
- OAuth2 client credentials flow allows Cloud Run services to securely communicate with other APIs or services.
- Google Cloud Service Accounts support workload identity federation for secure access.
Authenticating with Cloud Run
1. Select an Identity Provider (IdP)
Some well-known identity providers that support OAuth2 and OIDC are as follows:
- Google Identity Platform
- Auth0
- Okta
- Microsoft Azure AD
- Keycloak
For the purpose of this tutorial, we will use Google Identity Platform as the IdP.
2. Enable Identity-Aware Proxy (IAP)
Google’s Identity-Aware Proxy (IAP) helps you limit access to your Cloud Run service using Google authentication.
Steps to Enable IAP:
1. Enable IAP in your Google Cloud project:
gcloud services enable iap.googleapis.com
What Does It Do?
- This command activates the IAP API (iap.googleapis.com) for your Google Cloud project.
2. Deploy your Cloud Run service with authentication enabled:
gcloud run deploy my-service \
--add-cloudsql-instances=my-instance \
--service-account=my-service-account@my-project.iam.gserviceaccount.com
What Does It Do?
- gcloud run deploy my-service
- Deploys a new Cloud Run service named my-service.
- If the service already exists, this command updates it.
- –add-cloudsql-instances=my-instance
- Connects the Cloud Run service to a Cloud SQL instance named my-instance.
- This is required if your application needs to access a database hosted in Cloud SQL.
- –service-account=my-service-account@my-project.iam.gserviceaccount.com
- Assigns a specific service account to the Cloud Run service.
- This service account must have appropriate IAM roles to access Cloud SQL, such as roles/cloudsql.client.
3. Configure IAP by restricting access to authorized users:
gcloud projects add-iam-policy-binding my-project \
--member=user:example@gmail.com \
--role=roles/iap.httpsResourceAccessor
What Does It Do?
- gcloud projects add-iam-policy-binding my-project
- Adds an IAM policy binding (permission) to the Google Cloud project named my-project.
- Replace my-project with your actual Google Cloud project ID.
- –member=user:example@gmail.com
- Specifies the user who will receive the permission.
- –role=roles/iap.httpsResourceAccessor
- Grants the “IAP-secured Web App User” role (roles/iap.httpsResourceAccessor).
- This allows the specified user to access web applications protected by Identity-Aware Proxy (IAP).
- Without this role, the user will be blocked by IAP when trying to access a Cloud Run, App Engine, or Compute Engine backend.
3. Implement OAuth2 in Your Application
If you are not using IAP and want to implement OAuth2 authentication manually, follow these steps:
a. Register Your Application with Google OAuth
- Go to the Google Cloud Console.
- Navigate to APIs & Services > Credentials.
- Create a new OAuth 2.0 Client ID.
- Configure the Authorized Redirect URIs (e.g., https://your-service-url/callback).
- Note down the Client ID and Client Secret.
b. Implement OAuth2 Flow in Your Application
Your application should handle the OAuth2 authorization flow:
1. Redirect Users to the Authorization URL
from flask import Flask, redirect, request
import requests
import os
app = Flask(__name__)
CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
REDIRECT_URI = https://your-service-url/callback
AUTH_URL = https://accounts.google.com/o/oauth2/auth
@app.route("/login")
def login():
auth_url = (f"{AUTH_URL}?client_id={CLIENT_ID}&response_type=code"
f"&redirect_uri={REDIRECT_URI}&scope=openid email profile")
return redirect(auth_url)
What Does It Do?
1. Import Required Modules
from flask import Flask, redirect, request
import requests
import os
- Import all the necessary libraries.
2. Initialize Flask App
app = Flask(__name__)
- Creates an instance of a Flask web application.
3. Define OAuth2 Configuration Variables
CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
REDIRECT_URI = https://your-service-url/callback
AUTH_URL = "https://accounts.google.com/o/oauth2/auth" - CLIENT_ID: Retrieved from environment variables (GOOGLE_CLIENT_ID). This is the unique identifier for your app registered with Google.
- REDIRECT_URI: The callback URL where Google will redirect after authentication. This must be pre-configured in the Google OAuth settings.
- AUTH_URL: The Google OAuth2 authorization endpoint, used to initiate the authentication process.
4. Define the /login Route
@app.route("/login")
def login():
auth_url = (f"{AUTH_URL}?client_id={CLIENT_ID}&response_type=code"
f"&redirect_uri={REDIRECT_URI}&scope=openid email profile")
return redirect(auth_url) - Defines a /login route that users visit to start the authentication process.
- Builds the authorization URL dynamically:
- client_id={CLIENT_ID} → Specifies the registered application.
- response_type=code → Requests an authorization code (needed for exchanging tokens).
- redirect_uri={REDIRECT_URI} → Specifies where Google should send the user after authentication.
- scope=openid email profile → Requests access to the user’s OpenID, email, and profile information.
- Redirects the user to Google’s authentication page, where they can log in and approve access.
2. Handle the Callback and Exchange Code for Tokens
TOKEN_URL = https://oauth2.googleapis.com/token
CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET")
@app.route("/callback")
def callback():
code = request.args.get("code")
data = {
"code": code,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"redirect_uri": REDIRECT_URI,
"grant_type": "authorization_code"
}
response = requests.post(TOKEN_URL, data=data)
tokens = response.json()
return tokens
What Does It Do?
1. Define the Token Endpoint and Client Secret
TOKEN_URL = https://oauth2.googleapis.com/token
CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET")
- TOKEN_URL: This is the endpoint provided by Google to exchange the authorization code for access and ID tokens.
- CLIENT_SECRET: Retrieved from environment variables (GOOGLE_CLIENT_SECRET), it is used to authenticate the application during the token request.
2. Define the /callback Route
@app.route("/callback")
def callback():
code = request.args.get("code") - callback route: This is the URL where Google redirects the user after they authenticate.
- request.args.get(“code”): Extracts the code query parameter from the URL, which was sent by Google after successful login.
3. Prepare Data for Token Exchange
data = {
"code": code,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"redirect_uri": REDIRECT_URI,
"grant_type": "authorization_code"
} - This dictionary contains the required fields for exchanging the authorization code for tokens.
4. Send a Request to Exchange the Code for Tokens
response = requests.post(TOKEN_URL, data=data)
tokens = response.json()
- requests.post(TOKEN_URL, data=data): Sends a POST request to Google’s token endpoint with the required parameters.
- response.json(): Converts the response from Google into a JSON object containing authentication tokens.
5. Return the Tokens
return tokens
- Returns the tokens received from Google, which typically include:
- access_token → Used for API access.
- id_token → Contains user identity information (JWT format).
- expires_in → Token expiration time.
- refresh_token (if offline access is enabled) → Used to get a new access token without re-authentication.
4. Validate the ID Token
Once the access and ID tokens are obtained, you should validate the ID token to ensure its authenticity:
import jwt
from google.auth.transport import requests
from google.oauth2 import id_token
@app.route(“/profile”)
def profile():
token = request.headers.get(“Authorization”).split(“Bearer “)[1]
try:
id_info = id_token.verify_oauth2_token(token, requests.Request(), CLIENT_ID)
return id_info
except Exception as e:
return {“error”: “Invalid token”}, 401
What Does It Do?
1. Import Required Modules
import jwt
from google.auth.transport import requests
from google.oauth2 import id_token
- Import all the necessary libraries.
2. Define the /profile Route
@app.route("/profile")
def profile(): - This route handles authenticated user requests to fetch profile information.
3. Extract the Token from the Request
token = request.headers.get("Authorization").split("Bearer ")[1] - Retrieves the Authorization header from the HTTP request.
The token is expected in the format:
Authorization: Bearer <id_token>
- .split(“Bearer “)[1] extracts the actual token by removing the “Bearer ” prefix.
4. Verify the ID Token
try:
id_info = id_token.verify_oauth2_token(token, requests.Request(), CLIENT_ID)
- id_token.verify_oauth2_token(token, requests.Request(), CLIENT_ID):
- Validates the token’s signature using Google’s public keys.
- Ensures the token was issued for the correct CLIENT_ID and is not expired.
- Parses the token into a dictionary containing user identity data.
5. Return the User’s Information if Verified
return id_info
- If verification is successful, the decoded ID token (JSON format) is returned, typically containing:
{
"sub": "1234567890",
"name": "John Doe",
"email": "johndoe@example.com",
"picture": https://lh3.googleusercontent.com/a-/AOh14...
} 6. Handle Invalid or Expired Tokens
except Exception as e:
return {"error": "Invalid token"}, 401
- If verification fails due to an invalid or expired token, it returns a 401 Unauthorized error.
5. Deploy Your Application to Cloud Run
After implementing authentication, deploy the application to Cloud Run:
gcloud run deploy my-auth-service --source .
Conclusion
Implementing OAuth2 and OpenID Connect authentication in Cloud Run enhances security by ensuring only authenticated users can access your services. You can use Google Identity Platform and IAP for seamless authentication or implement OAuth2 manually using an external IdP. By following these steps, you can effectively secure your Cloud Run applications while providing a smooth authentication experience for users.
Containerize Legacy Application for Cloud Run – Steps & Benefits
Ever feel like you’re stuck with old technologies? Today, businesses are shifting their apps to the cloud. They do this for better performance, more scalability, and cost savings. But what about legacy applications? Many businesses depend on ‘legacy-applications’ – Those are reliable, monolithic systems were built long before cloud technologies were popular. You can modernize these apps and get the benefits of the cloud. One of the best ways to do this is by using containers and Google cloud Run.
In this blog, we will explain why containerizing legacy applications makes sense. We’ll also show you how to do it step by step. Plus, we’ll highlight the benefits of using Cloud Run.
Why use containers?
Think of a container as a lightweight package for your application. It includes everything the app needs to run: the code, settings, and any supporting dependencies/files. This makes it super portable.
Why Containerize a Legacy Application?
Most legacy applications were designed to run on traditional servers, which makes scaling, updating, or deploying them efficiently difficult. Containerization solves these problems by:
- Easy Scaling: Containers allow you to easily create copies of your app, handle increased traffic and scale up or down based on demand. No more server headaches!
- Simple Deployment: Moving the entire application with its dependencies into a single unit to a new environment becomes an easy job. This way, everything’s nicely packaged, it runs the same everywhere, reducing those frustrating “it works on my machine” moments.
- Cost Savings: Cloud Run is a serverless platform. This means you only pay when your app is running. This way you can reduce the cost of always-on servers!
- Enhanced Security: Containers provide isolation, by reducing the risk of other applications and other parts of your system affecting your legacy app.
- New Features: When applications are containerized, they can connect and use the new cloud services like – modern databases, security, networking, and app management tools.
Steps to Containerize Legacy Applications for Cloud Run



Step 1: Analyze Your App
Before you start containerizing your legacy application, you need to understand its architecture. Figure out:
- What programming language is it written in? (Java, Python, .NET, etc.)
- What dependencies does it have? (Database connections, libraries)
- Does it have specific OS configurations?
- Where does it store data? (Persistent storage or local files)
If your application depends heavily on the operating system, a multi-stage build may be necessary to keep the container lightweight.
Step 2: Create a Docker file
A Docker file is a script it tells Docker how your application should be pack into a container. Here’s an example for your Java-based legacy application:
Dockerfile:
FROM openjdk:17-jdk-slim # Use a small, efficient Java image
WORKDIR /app # Set the working directory inside the container
COPY target/my-legacy-app.jar my-legacy-app.jar # Copy your app's code
CMD ["java", "-jar", "my-legacy-app.jar"] # Command to run your app
For Java applications built with Spring Boot, you might need to add some more configurations to your Dockerfile to ensure your application’s ports are accessible from outside the container. This is because Spring Boot apps often run on a specific port (by default, 8080), and you need to make sure that port is expose correctly when the app is running inside a Docker container.
Step 3: Build and Test Your Container Locally
Once your Docker file is ready, build and test your container on your own machine to make sure everything works:
Bash:
docker build -t my-legacy-app. # Build the container image
docker run -p 8080:8080 my-legacy-app # Run the container
Step 4: Upload your container image to Google Container Registry (GCR).
Cloud Run requires your container image to be store in a registry. You can use Google Container Registry (GCR) or Artifact Registry:
gcloud auth configure-docker # Connect to your Google Cloud account
docker tag my-legacy-app gcr.io/YOUR_PROJECT_ID/my-legacy-app #Tag the container image
docker push gcr.io/YOUR_PROJECT_ID/my-legacy-app # Upload it to GCR
Step 5: Deploy on Cloud Run
Finally, deploy your containerized appl to Cloud Run:
gcloud run deploy my-legacy-app \
--image gcr.io/YOUR_PROJECT_ID/my-legacy-app \
--platform managed \
--region us-central1 \
--allow-unauthenticated
Cloud Run takes care of the rest, giving you a live, scalable app accessible over the internet.
Benefits of Running Containerize Legacy Applications on Cloud Run
- No Server Management – Google manages all the servers, so you can focus on your app, not on infrastructure.
- Pay-as-you-go – Only pay for the resources your app actually uses. Cloud Run charges only for the exact CPU and memory your application uses.
- Better Performance – Your legacy app can take advantage of Google’s high-speed network and powerful infrastructure.
- Security & Compliance – Built-in security features like IAM, automatic HTTPS, and DDoS protection.
- Easy Connections – Integrate your app with other Google Cloud services.
Conclusion
Containerize Legacy Applications doesn’t need to completely rewrite them from scratch. It means packaging them in a way that makes them more portable, scalable, and cost-effective. Deploying your containerized application on Cloud Run modernizes your infrastructure. You can still use your existing codebase.
Web Security for Front-End Best Practices & Protection
Introduction to Front-End Security
Web security is not just the back-end’s job. Front-end developers must also protect user data and stop attacks. Even though most checks happen on the server, strong front-end validation gives fast feedback. It is the first line of defense against threats like XSS and CSRF.
Input Validation and Sanitization
User input is a common attack point. Unchecked data can lead to problems like SQL injection, command injection, and XSS.
- Client-Side Validation: Check data types, formats (like emails or phone numbers), and input length. For example, validate an email field before accepting it.
- Input Sanitization: Clean the input to remove harmful code. Tools like DOMPurify can sanitize HTML or scripts. For example:
// Sanitize user input before displaying it
import DOMPurify from 'dompurify';
function renderUserContent(content) {
const safeContent = DOMPurify.sanitize(content);
document.getElementById('user-content').innerHTML = safeContent;
}
Remember, front-end checks help the user but must be backed by server-side validation. Attackers can bypass client-side scripts.
Preventing Cross-Site Scripting (XSS)
XSS happens when an attacker puts harmful scripts into web pages that others view. There are three types:
- Reflected XSS: The malicious code is sent in a request and quickly returned.
- Stored XSS: The bad code is saved on the server and later shown to users.
- DOM-based XSS: The attack happens on the client side when JavaScript uses unsafe data.
To prevent XSS:
- Escape/Encode Output:
Use safe methods when inserting data into HTML. For example:
// Unsafe: may allow XSS
element.innerHTML = userInput;
// Safe: escapes harmful code
element.textContent = userInput;
Frameworks like React and Vue escape content automatically. For example, in React:
// React escapes HTML by default
const UserComponent = () => <div>{userInput}</div>;
- Content Security Policy (CSP):
Use a CSP header to limit where scripts and other resources can load. For example:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://trusted-cdn.com; style-src 'self' https://trusted-cdn.com; img-src 'self' https://trusted-cdn.com data:;">
Cross-Site Request Forgery (CSRF) Protection
CSRF tricks logged-in users into taking actions they did not intend. To prevent this:
- Anti-CSRF Tokens:
Every form or request should have a unique token. The server checks this token to ensure the request is genuine.
When using AJAX, send the token in the header:
javascript
CopyEdit
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
fetch('/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify(data)
});
- SameSite Cookie Attribute:
Set cookies with the SameSite attribute to control when they are sent. Use “Strict” or “Lax” to stop unwanted cookie sharing.
Authentication and Authorization
Securing user accounts and managing access are key.
- Authentication:
Always use HTTPS for secure data transfer. Consider JSON Web Tokens (JWT) or OAuth2 for managing logins. Multi-factor authentication adds extra security. Also, enforce strong password rules and give real-time feedback on password strength. For example:
// Simple password strength checker
function checkPasswordStrength(password) {
const criteria = {
length: password.length >= 12,
uppercase: /[A-Z]/.test(password),
lowercase: /[a-z]/.test(password),
numbers: /[0-9]/.test(password),
special: /[^A-Za-z0-9]/.test(password)
};
const strength = Object.values(criteria).filter(Boolean).length;
return {
score: strength,
feedback: {
warning: strength < 3 ? 'Password is too weak' : '',
suggestions: strength < 3 ? [
'Use at least 12 characters',
'Mix uppercase and lowercase letters',
'Include numbers and special characters'
] : []
}
};
}
- Authorization:
Use role-based access control (RBAC). Show users only the parts of the app they are allowed to see. This can be done by conditionally rendering UI elements based on the user’s role.
Security Headers and HTTPS
Security headers add extra protection. They tell browsers how to handle content and block attacks.



- Strict-Transport-Security (HSTS):
Forces browsers to use HTTPS. - X-Content-Type-Options:
Stops browsers from guessing content types. - X-Frame-Options:
Stops other sites from embedding your website. - X-XSS-Protection:
Enables the browser’s built-in XSS filter. - Referrer-Policy and Permissions-Policy:
Control what referrer data is sent and which browser features can be used.
These headers can also be added via meta tags:
html
CopyEdit
<meta http-equiv="X-Frame-Options" content="DENY">
<meta http-equiv="X-XSS-Protection" content="1; mode=block">
<meta http-equiv="X-Content-Type-Options" content="nosniff">
<meta http-equiv="Referrer-Policy" content="strict-origin-when-cross-origin">
Third-Party Dependencies and Secure Storage
Many apps use third-party libraries. Keep them secure by:
- Auditing dependencies with tools like npm audit or Snyk.
- Pinning dependency versions and using lockfiles (like package-lock.json).
- Using subresource integrity (SRI) for scripts from CDNs:
html
CopyEdit
<script src="https://cdn.example.com/library.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"></script>
For sensitive data, store API keys in environment variables. Prefer server-side storage over client-side. Use secure methods like session storage or HTTP-only cookies, and encrypt data if needed. For example:
async function encryptData(data, key) {
const encoded = new TextEncoder().encode(data);
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const encryptedData = await window.crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
encoded
);
return { encryptedData, iv };
} Avoid storing tokens in local storage to reduce the risk of misuse.
Web security Testing and Regular Audits
Security is not a one-time task. It needs regular checks and updates.
- Static Analysis Tools (SAST):
Use ESLint plugins for security to catch issues during development. - Dynamic Testing (DAST):
Tools like OWASP ZAP and Burp Suite can find runtime vulnerabilities. - Manual Testing:
Regular code reviews and penetration tests help find subtle issues.
Here’s a sample ESLint configuration with security plugins:
// .eslintrc.js configuration
module.exports = {
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:security/recommended'
],
plugins: [
'react',
'security'
],
rules: {
'security/detect-object-injection': 'error',
'security/detect-non-literal-regexp': 'error',
'security/detect-unsafe-regex': 'error'
}
};
Conclusion
Web security is a shared duty. Front-end developers must check inputs, stop XSS/CSRF, secure authentication, and test often. By following these practices and using the code examples, you can build strong web applications that protect users and their data.
Securing C++ Applications: Practical Techniques for Modern Threats
C++ remains a cornerstone of software development, powering critical systems worldwide. My own journey with C++ has highlighted the crucial link between its power and the responsibility for application security.
By “modern threats,” we refer to the evolving landscape of vulnerabilities that exploit weaknesses in software to achieve malicious goals. These include everything from classic memory corruption issues like buffer overflows and use-after-free errors, to injection attacks (SQL, command), and more subtle dangers like format string vulnerabilities and integer overflows.
This blog post explores key techniques for securing C++ applications against these modern threats. We’ll examine memory management vulnerabilities (leaks, dangling pointers), the persistent challenge of buffer overflows, and the subtle dangers of format string vulnerabilities. We’ll also discuss the critical importance of avoiding undefined behaviour, leveraging compiler security features, and ditching deprecated libraries.
1. Memory Leaks and Dangling Pointers: The Silent Dangers
Memory leaks, where allocated memory is not properly released, can lead to resource exhaustion and application crashes. Dangling pointers, which point to memory that has already been freed, can cause unpredictable behavior and security vulnerabilities. These vulnerabilities are often harder to detect than buffer overflows, as their effects might not be immediately apparent.
Mitigation: Employ smart pointers (std::unique_ptr, std::shared_ptr, std::weak_ptr) to automate memory management. These smart pointers automatically deallocate memory when it’s no longer needed, preventing both memory leaks and dangling pointers. This approach significantly reduces the burden of manual memory management and minimizes the risk of these types of errors.
Example:
void memoryLeak() {
int* ptr = new int; // Memory allocated, but never freed
*ptr = 10;
// ... some code ... Oops, forgot to delete ptr; memory leak!
}
void danglingPointer() {
int* ptr = new int;
*ptr = 20;
delete ptr;
// ... some code ...
// ptr now a dangling pointer! Accessing it is undefined behavior
// std::cout << *ptr << std::endl; // Dangerous!
}
void memorySafe() {
std::unique_ptr<int> ptr = std::make_unique<int>(30); // Use smart pointers
// No need to manually delete; memory is automatically managed
std::cout << *ptr << std::endl;
} 2. Buffer Overflows: The Persistent Challenge
Buffer overflows remain a significant threat. They occur when data is written beyond the allocated buffer, potentially overwriting adjacent memory and allowing attackers to inject malicious code. This is particularly relevant in C++ due to its manual memory management. The consequences can range from application crashes to complete system compromise.
Mitigation: Meticulous memory management is crucial. Always verify the size of input data before copying it into a buffer. Avoid functions like strcpy and sprintf that don’t perform bounds checking. Instead, use safer alternatives like strncpy or, even better, leverage C++’s standard library features like std::string and its associated methods. These provide automatic memory management and bounds checking, significantly reducing the risk of buffer overflows. Using RAII (Resource Acquisition Is Initialization) principles with smart pointers is also highly recommended.
Example:
void bufferOverflowVulnerable(char* input) {
char buffer[16];
// Vulnerable: No bounds checking
strcpy(buffer, input); // Potentially dangerous!
std::cout << buffer << std::endl;
}
void bufferOverflowSafe(const std::string& input) {
std::string buffer;
buffer = input; // Safe: std::string handles memory automatically
std::cout << buffer << std::endl;
// Or using strncpy (less preferred than std::string)
char c_buffer[16];
strncpy(c_buffer, input.c_str(), sizeof(c_buffer) - 1); // Important to null-terminate!
c_buffer[sizeof(c_buffer) - 1] = '\0'; // Ensure null termination
std::cout << c_buffer << std::endl;
} 3. Format String Vulnerabilities: A Subtle but Serious Risk
Format string vulnerabilities arise when user-supplied input is directly used as a format string in functions like printf or fprintf. Attackers can exploit this to read from or write to arbitrary memory locations, potentially gaining control of the application. This vulnerability can be surprisingly easy to introduce if proper care is not taken.
Mitigation: Never use user-supplied input directly as a format string. Always use a fixed format string and pass user-supplied data as arguments. For example, instead of printf(user_input), use printf(“%s”, user_input). This simple change effectively prevents format string vulnerabilities.
Example:
void formatStringVulnerable(const char* user_input) {
// Vulnerable: User input directly used as format string
printf(user_input); // Extremely dangerous!
}
void formatStringSafe(const char* user_input) {
// Safe: Fixed format string, user input passed as argument
printf("%s", user_input);
} 4. Avoid Undefined Behaviour at All Costs
Undefined behavior (UB) in C++ refers to program behavior that the C++ standard doesn’t define. When UB occurs, anything can happen – crashes, incorrect results, or seemingly correct execution that fails intermittently. Critically, UB can create exploitable security vulnerabilities. Compilers are allowed to make any assumption when encountering UB, which can lead to unexpected code paths and vulnerabilities.
Example:
1. Dereferencing a null pointer
int* ptr = nullptr;
*ptr = 10; // Undefined behavior!
Mitigation: Always check if a pointer is valid before dereferencing it:
int* ptr = nullptr;
if (ptr != nullptr) {
*ptr = 10; // Safe
}
2. Out-of-bounds array access:
int arr[10];
arr[10] = 20; // Undefined behavior! (Valid indices are 0 to 9)
Mitigation: Carefully check array indices:
int arr[10];
for (int i = 0; i < 10; ++i) {
arr[i] = i * 2; // Safe
}
3. Data Race
int counter = 0; // Shared variable
// Thread 1:
counter++;
// Thread 2:
counter++; // Data race! Undefined behavior!
Mitigation: Use proper synchronization (e.g., mutexes):
std::mutex m;
int counter = 0; // Shared variable
// In each thread:
{
std::lock_guard<std::mutex> lock(m);
counter++; // Safe
}
4. Integer Overflow (signed)
int x = INT_MAX; // Maximum value for int
x++; // Undefined behavior!
Mitigation: Check before incrementing, or use wider types
int x = INT_MAX;
if (x < INT_MAX) {
x++; // Safe
}
// Or, for more predictable overflow behavior:
unsigned int y = UINT_MAX;
y++; // Well-defined wrap-around behavior
5. Incorrect type casting
int i = 10;
float* fptr = reinterpret_cast<float*>(&i); // Potentially dangerous!
*fptr = 3.14f; // Undefined behavior!
Mitigation: Be very careful with type casting. Use static_cast when possible.
5.Enable Compiler Security Features
Modern C++ compilers offer security-focused flags that can significantly improve application security. These flags help detect potential vulnerabilities at compile time or runtime. Enable flags like -fstack-protector-strong (for stack buffer overflow protection), -fsanitize=address (ASan for memory error detection), -fsanitize=undefined (UBSan for undefined behavior detection), -fPIE and -pie (for Position Independent Executable), and -D_FORTIFY_SOURCE=2 (for fortified standard library functions). Treating warnings as errors (-Werror) also promotes cleaner code. These flags, used together, provide a strong layer of defense against common vulnerabilities. Consult your compiler’s documentation for details and consider the performance impact in production.
6. Ditch Deprecated Libraries
Deprecated libraries, no longer maintained, are breeding grounds for vulnerabilities. They often contain known, unpatched flaws, making them easy targets for attackers. Replace deprecated libraries with modern, actively maintained alternatives.
For instance, instead of using outdated network libraries like boost::asio, prefer std::async for concurrency and std::filesystem for file operations.
Secure Coding Practices: The Foundation of Security
Adhering to secure coding practices is essential for building robust and secure C++ applications. This includes:
- Principle of Least Privilege: Grant only the necessary permissions to users and processes.
- Defense in Depth: Implement multiple layers of security to protect against different types of attacks.
- Code Reviews: Conduct thorough code reviews to identify and fix potential security vulnerabilities.
- Static and Dynamic Analysis: Use static and dynamic analysis tools to detect security flaws in your code.
Conclusion:
Security is an ongoing process. New threats and vulnerabilities are constantly being discovered. It’s crucial to stay up-to-date with the latest security advisories and best practices. Regularly update your libraries and tools to patch known vulnerabilities.
Building secure C++ applications requires a proactive and comprehensive approach. By understanding common vulnerabilities and implementing the techniques discussed in this blog post, you can significantly improve the security of your applications and protect them from modern threats. Remember, security is not an afterthought; it should be an integral part of the software development lifecycle from the very beginning. My journey with C++ has taught me many things, and the importance of secure coding is certainly one of the most valuable lessons.
Authentication and Authorization in a web UI with best practices.
When building web applications, ensuring the right users have the right access is crucial for both security and functionality. Two key concepts that help secure web apps are authentication and authorization in web UI. While these terms are often used interchangeably, they serve different roles. Let’s use an analogy of a streaming application to better understand the differences.
Authentication: Verifying Who You Are
Authentication is the process of verifying a user’s identity—answering the question, “Who are you?” For example, when you log into a streaming app, you enter your username and password to prove who you are. This is the first step in allowing users to access the system.
Example: Authentication with JWT (JSON Web Token)
// Node.js Express authentication endpoint (simplified)
const jwt = require('jsonwebtoken');
app.post('/login', (req, res) => {
const { username, password } = req.body; // Verify username and password const
// Create JWT token
const token = jwt.sign(
{ username: user.username },
'your-secret-key',
{ expiresIn: '1h' });
res.json({ token });
});
Common Authentication Methods:
- Username and Password: The most basic form, where access is granted if the entered credentials match stored ones.
- Two-Factor Authentication (2FA): Adds an extra layer of security by requiring a second form of identification, such as a code sent to your phone.
- OAuth/OpenID Connect: Allows users to log in via third-party services like Google or Facebook, so they don’t need to create new passwords.
Authorization: Determining What You Can Do
Once authenticated, authorization determines what actions or content the user can access—answering the question, “What can you do now that I know who you are?”
For instance, in a streaming app, after logging in, your subscription type (e.g., free, premium, admin) defines what content you can access. A free user might only have access to standard-definition content, while a premium user can stream in 4K or access exclusive features.
Example: Role-Based Authorization with JWT
…
// Protected route for premium users
app.get('/premium-content',
authenticateJWT,
authorizeRole('premium'),
(req, res) => { res.send('This is premium content.');
});
…
Types of Authorization:
- Role-Based Access Control (RBAC): Users are assigned roles (e.g., Free User, Premium User, Admin) and each role comes with specific permissions.
- Attribute-Based Access Control (ABAC): Access is based on various attributes such as location or subscription level.
- Access Control Lists (ACLs): Defines which users or groups have access to specific resources, such as content or settings.
Authentication and Authorization in a web UI
1. Authentication in the UI
The login screen is the entry point for authentication in your web application, just like the login page in a streaming app.
- Login Forms: Keep the form simple and easy to access.
- Error Feedback: Provide clear, user-friendly messages for incorrect credentials. Avoid revealing too much information, like whether the username or password is incorrect.
- Session Management: Once authenticated, users should stay logged in unless they log out. Implement session timeouts for security.
Example: Here’s how you might handle it with a simple login form:
<form id="loginForm">
<input type="text" id="username" placeholder="Username" required>
<input type="password" id="password" placeholder="Password" required>
<button type="submit">Log In</button>
</form>
<script>
document.getElementById('loginForm').addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
const res = await fetch('/login', {
method: 'POST',
body: JSON.stringify({ username, password }),
headers: { 'Content-Type': 'application/json' },
});
const data = await res.json();
if (data.token) {
localStorage.setItem('token', data.token);
window.location.href = '/dashboard'; // Redirect to user dashboard
} else {
alert('Invalid credentials');
}
});
</script>
2. Authorization in the UI
Authorization controls what content and features users can see, depending on their role.
- Conditional UI Rendering: Tailor the UI to the user’s role. For example, free users might see standard content, while premium users can access higher-quality streams or exclusive shows.
- Role-Specific Routes: Only users with the appropriate role should access certain features. For example, admins may access a “Manage Content” page, while free users cannot.
- Access Denied Pages: If users try to access restricted content, show an access denied page with a clear explanation.
Example: How to conditionally render content
// JavaScript to handle role-based UI rendering
const token = localStorage.getItem('token');
if (token) {
const decoded = jwt.decode(token);
if (decoded.role === 'premium') {
document.getElementById('premiumContent').style.display = 'block';
} else {
document.getElementById('premiumContent').style.display = 'none';
}
} else { alert('Please log in to access content'); }
Best Practices for Authentication and Authorization in a web UI
- Use Strong Passwords: Enforce password complexity rules.
- Secure Authentication Flow: Use HTTPS and secure password storage (e.g., bcrypt).
- Minimize Permissions: Apply the principle of least privilege—grant users only the access they need.
- Use OAuth for Third-Party Logins: Implement secure protocols like OAuth for third-party logins.
Conclusion
Authentication and authorization are key to securing your web app. By ensuring only the right users can access the right areas and content, you protect both your system and users. Regularly review these processes to maintain a secure and user-friendly experience.
Software Development Challenges & How to Avoid Them.
Building a software product is like nurturing a growing tree. Initially small and manageable, it soon grows, branching out and becoming more complex to maintain. As software scales, it faces technical and operational Software Development Challenges that can feel overwhelming. Here, we explore common issues, real-world examples, and practical solutions to help your software thrive.
1. Performance Degradation
Problem: Imagine a crowded restaurant with just one chef. Orders pile up, delays mount, and customers leave frustrated. Similarly, software development challenges arise when a system can’t handle increasing demand, leading to slow response times, laggy interfaces, and frequent timeouts.
Scenario: An e-commerce platform hosts a flash sale, and thousands of users flock to grab deals. The site slows down, pages take forever to load, and customers abandon their carts, resulting in lost sales.
Real Example: Amazon’s Prime Day in 2018 faced intermittent outages due to overwhelming traffic.



Solution:
- Use load testing tools like JMeter or K6 to simulate traffic and identify bottlenecks.
- Implement caching layers (e.g., Redis) to reduce database load.
- Leverage cloud auto-scaling for dynamic resource allocation.
- Adopt frameworks like Kafka or RabbitMQ for asynchronous processing to handle spikes in background tasks efficiently.
- Helpful tool:- Neural Prophet an AI-based forecasting tool for analyzing and predicting system loads.
2. Scalability Limitations
Problem: Your app is like a party venue designed for 50 people. When 500 show up, chaos ensues.
Scenario: A viral video drives millions to a social media app overnight. The app crashes, damaging your brand’s reputation.
Real Example: Twitter’s “Fail Whale” often appeared during major events, highlighting scalability issues.



Solution:
- Switch to microservices architecture to scale components independently.
- Use content delivery networks (CDNs) like Cloudflare to distribute traffic globally.
- Adopt Docker and Kubernetes for containerization and orchestration, enabling seamless scaling.
- Implement database sharding to spread data across multiple servers for faster access.
- Helpful tool:- Kubernetes and combining it with AI tools like Dynatrace or Prometheus would be ideal.
3. Codebase Complexity
Problem: Over time, your codebase becomes a jungle—dense, tangled, and unmanageable.
Scenario: A financial app takes weeks to add a small feature because every change risks breaking something else. Poor documentation exacerbates the issue.
Real Example: Microsoft’s Windows Vista faced delays and performance issues due to its overly complex codebase.
Solution:
- Regularly refactor code to simplify and optimize.
- Maintain comprehensive documentation using tools like JSDoc or Doxygen.
- Follow modular design principles like SOLID to keep code manageable.
- Helpful tool: SonarQube and pairing it with AI tools like GitHub Copilot can enhance productivity.
4. Database Bottlenecks
Problem: As data grows, your database can choke on slow queries, poor indexing, or overloaded servers.
Scenario: A ride-hailing app experiences delays during peak hours because fetching driver availability takes too long. Users cancel rides, causing revenue loss.
Real Example: Uber resolved similar issues by adopting NoSQL databases like Cassandra, designed for massive scalability.
Solution:
- Add indexes to speed up query execution.
- Use read replicas to distribute the load.
- Optimize queries using tools like SQL Profiler.
- Helpful tool: Cassandra for NoSQL databases, SQL Profiler for RDBMS.
5. Security Vulnerabilities
Problem: As software grows, its attack surface expands, making it easier for hackers to exploit vulnerabilities.
Scenario: A SaaS platform fails to encrypt user data, leading to a data breach, fines, and loss of trust.
Real Example: The 2017 Equifax breach exposed sensitive data of 147 million people due to an unpatched vulnerability.



Solution:
- Conduct regular security audits and penetration tests.
- Train developers in secure coding practices (e.g., OWASP).
- Use encryption and implement strong access controls.
- Periodically upgrade packages and libraries to address vulnerabilities.
- Helpful tool: OWASP ZAP (Zed Attack Proxy)
6. Poor API Design
Problem: APIs act as bridges between systems, but unclear or poorly documented ones make integrations difficult.
Scenario: A payment system launches a refund API, but vague documentation confuses vendors, delaying adoption.
Solution:
- Establish consistent naming conventions (e.g., camelCase or snake_case).
- Use standardized error response formats with clear codes and messages.
- Implement proper versioning (e.g., /v1/, /v2/) for breaking changes.
- Add comprehensive documentation using OpenAPI/Swagger.
- Enable rate limiting and implement authentication/authorization.
- Use pagination for large datasets and caching for frequently accessed data.
- Follow REST principles with clear resource hierarchies.
- Implement automated endpoint testing.
- Create API style guides and governance processes.
7. Caching Invalidation
Problem: Caching speeds things up, but outdated or inconsistent caches can confuse users.
Scenario: A news site updates its homepage with breaking news, but users still see yesterday’s headlines due to outdated caches.
Real Example: Facebook’s engineers developed sophisticated cache invalidation techniques to ensure real-time updates.



Solution:
- Define cache expiration policies to refresh data regularly.
- Use event-driven caching to invalidate caches during updates.
- Test cache consistency during development.
- Helpful tool : Redis is ideal for quick, in-memory data access (caching) and real-time data storage.
8. Memory Leaks
Problem: Memory leaks are like slow leaks in a tire—your system works fine initially but eventually grinds to a halt.
Scenario: A video conferencing app crashes during large meetings because memory leaks in the video module consume all resources.
Real Example: Zoom optimized its memory management to support massive virtual meetings.
Solution:
- Use profiling tools like Valgrind to identify leaks.
- Optimize memory usage with garbage collection.
- Stress test under heavy loads to detect issues.
Conclusion
Scaling a software product is challenging but rewarding. Software Development Challenges like performance degradation and security vulnerabilities are opportunities to learn and grow. By adopting scalable architectures, robust security practices, and efficient coding principles, you can ensure your software remains reliable and user-friendly.
Every Software Development Challenge is a stepping stone to creating a resilient, thriving product. So embrace the bumps and build something remarkable!
Integrating Flutter with Backend Services for Seamless SaaS Applications
How to Build Redux Application with Angular 2+
Contents:
- Introduction
- Passing Data using Properties
- Redux Architecture
- Install Redux Packages
- Build an App with Angular and Redux
- Conclusion
Single Page Application(SPA) is an excellent feature to build modern web-based apps. Here, the data level dependency moves from the server-side to the browser level as much as possible, to improve the application performance.
Modern SPA application frameworks like Angular, React use the component-based architecture that divides our application into small-small sections. Each component contains its own HTML page, stylesheet, and its data. This is an organized approach, it easily divides our application into small sections and allows us to reuse these components into our application.
If the size of the application is small then component-based architecture is appropriate. It is difficult to maintain the state of each component for larger applications.
PASSING DATA USING PROPERTIES



In Angular, we use the input properties to pass the data and output properties to receive the data in the component tree. Consider the above diagram, we have 3 components in the component tree and we want to pass some data to its grandchild component i.e. to component C. For this, first we should pass this data to its child component that is parent component of the grandchild i.e. component B, then further the parent component passes this data to its own child component using the “input” property.
Components are Inflexible
It becomes inflexible while we use properties for passing data between components. If we want to reuse these components, it is mandatory to pass the values to them.
Also, it’s difficult to understand the assignment of a component that contains several input properties.
Data is maintained Synchronously
If more than one component uses the same state and one of them changes the state, it is requisite to notify the rest of the components to update their states. This approach is significant and an expensive task.
State of Application
The state is divided at the component level. If each and every component has its own state, the complexity increases for checking the state of the complete application.
Redundant Data
If we have multiple components and each component contains its individual copy of the data, then there is a possibility of data duplication.
All the above demerits of the application’s inconsistent states are difficult to manage the state. To overcome this, we need a solution to manage the state of our application.
REDUX ARCHITECTURE
Redux is an open-source JavaScript library developed to maintain the state of the application. Redux works on the centralized data mechanism. Redux maintains the state of an entire application in a single immutable state tree (object), which can’t be changed directly. When something changes, a new object is created (using actions and reducers).
Building Block of the Redux
Before starting the development with Redux, we must have a basic knowledge of the building blocks of the Redux architecture. So let’s understand this.



Actions
Actions are payloads of information that send data from our application to the store using the “Store.dispatch()” method. Redux only enforces the “type” property, the structure of the action can be whatever works for you and your application. To make the dispatch action easier, special functions called “Action Creators” are commonly used.



Reducers
Actions only describe that “something” happened; reducers are the ones to specify how the application state changes in response to those actions. Reducers are pure functions, meaning they cannot modify input data or be altered by any external state like databases, document object model events, global constants, etc.
They have the following signature:



Store
A store is a simple JavaScript object that contains the state of the whole application. We can update the state of the application using the “dispatch (action, payload)” method. Dispatcher every time update the previous state of the application and generate a new state.
The store holds the application state. And allows it to access that info through the getState() function and update with the dispatch() method. Store registers the listeners with the subscribe (listener) function & unregisters them by using the object returned by the register function. Redux provides a helper createStore for creating a store. Its signature is as follows:



INSTALL REDUX PACKAGES
For creating building block like Store, Action and Reducer for our application we need to install Redux package first.
To install Redux for the application run the following command:
npm install redux @angular-redux/store –save
BUILD AN APP WITH ANGULAR AND REDUX
1.Define the application state models:



2. Generate the product’s action creators:



3. Add the reducer:



4. Import the Store Module into our app’s main module:



5. Finally, inject the “Store” service into our components and modules and request data from the state using the “store.select.”:



CONCLUSION
Here, with a simple example, we have tried introducing how Redux can be used with Angular. If we are going to create a large application in Angular then it is better to use a state management architecture that can manage the state of our application. This state management technique will increase the performance of our application. But if our application is not large then we should not use this state management technique.























