O Sertão será Cloud

Connecting to Azure Database for MySQL with Azure Functions Using Native Drivers and Managed…

Connecting to Azure Database for MySQL with Azure Functions Using Native Drivers and Managed Identity

Welcome to our practical guide on how to connect Azure Functions to Azure MySQL using secure and scalable authentication techniques. In this article, you will learn how to set up your development environment and implement a robust Azure Function with MySQL. We will explore authentication methods such as managed identity and service principal. 🌐

What is Azure Database for MySQL?

Azure Database for MySQL is a managed relational database service based on the popular MySQL database management system. It offers secure and reliable infrastructure for hosting your applications in the cloud, focusing on simplicity and performance.

Key Features and Benefits:

  • Simplified Administration: Azure for MySQL handles critical tasks like automatic backups, software updates, and disaster recovery, allowing developers to focus on growing the application.
  • Elastic Scalability: Increase or decrease computing and storage capacity as needed without significant interruptions, which is ideal for growing applications.
  • Built-in High Availability: With support for geographic replication and automatic failover, the database ensures that your application is always available, even in regional failure scenarios.
  • World-Class Security: Your data is protected with SSL/TLS encryption in transit and at rest, along with access control through integration with Microsoft Entra ID (formerly Azure Active Directory), offering identity-based authentication and access control.
  • Optimized Performance: With built-in monitoring and tuning, Azure MySQL allows fine-tuning database performance, ensuring it meets your response and workload needs.
  • Compatibility and Flexibility: Fully compatible with the standard version of MySQL, allowing you to easily bring your existing MySQL applications to the cloud without major adjustments. It also supports popular extensions and features such as partitioned tables, asynchronous replication, and complex queries.

Common Use Cases:

  • Web and Mobile Applications: Ideal for applications that require a scalable and high-performance relational database.
  • Real-time Data Analysis: For businesses that need to perform complex SQL queries and dynamic reporting on large volumes of data.
  • E-commerce and ERP Systems: Enterprise applications that need fast and consistent transactions, ensuring high availability and data integrity.

Step 1: Setting Up the Environment

Ensure you have the following dependencies installed:

npm install @azure/identity mysql2

Step 2: Creating the CredentialProvider Abstraction

We will create a CredentialProvider interface that defines a getCredential method.

import { TokenCredential } from "@azure/identity";

/**
* Interface for providing Azure credentials.
*/
interface CredentialProvider {
/**
* Retrieves the Azure credential.
* @returns {TokenCredential} The Azure credential.
*/
getCredential(): TokenCredential;
}

Step 3: Implementing Credential Providers

We will create different implementations of CredentialProvider for various authentication methods.

Method 1: SystemAssignedManagedIdentityCredentialProvider

import { DefaultAzureCredential } from "@azure/identity";

/**
* Provides credentials for system-assigned managed identity.
*/
class SystemAssignedManagedIdentityCredentialProvider implements CredentialProvider {
/**
* Retrieves the Azure credential for system-assigned managed identity.
* @returns {TokenCredential} The Azure credential.
*/
getCredential(): TokenCredential {
return new DefaultAzureCredential();
}
}

Method 2: UserAssignedManagedIdentityCredentialProvider

import { DefaultAzureCredential } from "@azure/identity";

/**
* Provides credentials for user-assigned managed identity.
*/
class UserAssignedManagedIdentityCredentialProvider implements CredentialProvider {
private clientId: string;
/**
* Creates an instance of UserAssignedManagedIdentityCredentialProvider.
* @param {string} clientId - The client ID of the user-assigned managed identity.
*/
constructor(clientId: string) {
this.clientId = clientId;
}
/**
* Retrieves the Azure credential for user-assigned managed identity.
* @returns {TokenCredential} The Azure credential.
*/
getCredential(): TokenCredential {
return new DefaultAzureCredential({
managedIdentityClientId: this.clientId
});
}
};

Method 3: ServicePrincipalCredentialProvider

import { ClientSecretCredential } from "@azure/identity";
/**
* Provides credentials for service principal authentication.
*/
class ServicePrincipalCredentialProvider implements CredentialProvider {
private tenantId: string;
private clientId: string;
private clientSecret: string;
/**
* Creates an instance of ServicePrincipalCredentialProvider.
* @param {string} tenantId - The tenant ID.
* @param {string} clientId - The client ID.
* @param {string} clientSecret - The client secret.
*/
constructor(tenantId: string, clientId: string, clientSecret: string) {
this.tenantId = tenantId;
this.clientId = clientId;
this.clientSecret = clientSecret;
}
/**
* Retrieves the Azure credential for service principal authentication.
* @returns {TokenCredential} The Azure credential.
*/
getCredential(): TokenCredential {
return new ClientSecretCredential(this.tenantId, this.clientId, this.clientSecret);
}
}

Step 4: Architecture with Credential Providers

Here is a factory to instantiate the MySQL client with the CredentialProvider.

import mysql from 'mysql2/promise';
import { TokenCredential } from '@azure/identity';

/**
* Factory for creating MySQL client instances.
*
* @remarks
* This class uses the Factory pattern to instantiate MySQL clients with
* credentials provided by Azure Identity. The goal is to centralize the
* connection creation logic, making the code more modular and reusable.
*/
class MySQLClientFactory {
private host: string;
private user: string;
private database: string;
private port: number;
private ssl: boolean;
private credentialProvider: any;

/**
* Creates a new instance of MySQLClientFactory.
*
* @param host - The MySQL database host.
* @param user - The database user name.
* @param database - The database name.
* @param port - The port used for MySQL connection.
* @param ssl - Indicates if SSL should be used.
* @param credentialProvider - The Azure credential provider.
*/
constructor(host: string, user: string, database: string, port: number, ssl: boolean, credentialProvider: any) {
this.host = host;
this.user = user;
this.database = database;
this.port = port;
this.ssl = ssl;
this.credentialProvider = credentialProvider;
}

/**
* Creates and returns an instance of the MySQL client.
*
* @returns A promise that resolves with the configured MySQL client instance.
*/
async createClient() {
const credential = this.credentialProvider.getCredential();
const accessToken = await credential.getToken('https://ossrdbms-aad.database.windows.net/.default');

const client = await mysql.createConnection({
host: this.host,
user: this.user,
password: accessToken.token,
database: this.database,
port: this.port,
ssl: this.ssl
});

return client;
}
}

Step 5: Using the Factory in an Azure Function

Let’s integrate this into an Azure Function, using Managed Identity.

Example 1: Azure Function with System-assigned Managed Identity

import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { MySQLClientFactory } from "./MySQLClientFactory";
import { SystemAssignedManagedIdentityCredentialProvider } from "./SystemAssignedManagedIdentityCredentialProvider";

const host = process.env.AZURE_MYSQL_HOST;
const user = process.env.AZURE_MYSQL_USER;
const database = process.env.AZURE_MYSQL_DATABASE;
const port = Number(process.env.AZURE_MYSQL_PORT);
const ssl = Boolean(process.env.AZURE_MYSQL_SSL);


/**
* Azure HTTP Function using managed identity to access Azure MySQL.
*
* @param context - The Azure Function context.
* @param req - The HTTP request.
*
* @remarks
* This Azure Function demonstrates how to use a managed identity to authenticate
* with Azure MySQL without the need for passwords or secret keys.
*
* @returns A promise that resolves when the function is complete.
*/
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
const credentialProvider = new SystemAssignedManagedIdentityCredentialProvider();

const clientFactory = new MySQLClientFactory(
host,
user,
database,
port,
ssl,
credentialProvider
);

const client = await clientFactory.createClient();
await client.connect();

// Execute database operations
await client.end();

context.res = {
body: "Azure Function using system-assigned managed identity to access Azure MySQL"
};
};

export default httpTrigger;

Example 2: Azure Function with User-assigned Managed Identity

import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { MySQLClientFactory } from "./MySQLClientFactory";
import { UserAssignedManagedIdentityCredentialProvider } from "./UserAssignedManagedIdentityCredentialProvider";

const host = process.env.AZURE_MYSQL_HOST;
const user = process.env.AZURE_MYSQL_USER;
const database = process.env.AZURE_MYSQL_DATABASE;
const port = Number(process.env.AZURE_MYSQL_PORT);
const ssl = Boolean(process.env.AZURE_MYSQL_SSL);
const clientId = process.env.AZURE_MYSQL_CLIENTID;

/**
* Azure Function using user-assigned managed identity to access Azure MySQL.
*
* @param context - The Azure Function context.
* @param req - The HTTP request.
*
* @returns A promise that resolves when the function is complete.
*/
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
const credentialProvider = new UserAssignedManagedIdentityCredentialProvider(
clientId,
);

const clientFactory = new MySQLClientFactory(
host,
user,
database,
port,
ssl,
credentialProvider
);

const client = await clientFactory.createClient();
await client.connect();

// Execute database operations
await client.end();

context.res = {
body: "Azure Function using user-assigned managed identity to access Azure MySQL"
};
};

export default httpTrigger;

Example 3: Azure Function with Service Principal

import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { MySQLClientFactory } from "./MySQLClientFactory";
import { ServicePrincipalCredentialProvider } from "./ServicePrincipalCredentialProvider";

const host = process.env.AZURE_MYSQL_HOST;
const user = process.env.AZURE_MYSQL_USER;
const database = process.env.AZURE_MYSQL_DATABASE;
const port = Number(process.env.AZURE_MYSQL_PORT);
const ssl = Boolean(process.env.AZURE_MYSQL_SSL);
const tenantId = process.env.AZURE_MYSQL_TENANTID;
const clientId = process.env.AZURE_MYSQL_CLIENTID;
const clientSecret = process.env.AZURE_MYSQL_CLIENTSECRET;

/**
* Azure Function using Service Principal to access Azure MySQL.
*
* @param context - The Azure Function context.
* @param req - The HTTP request.
*
* @remarks
* This Azure Function demonstrates how to use a Service Principal for
* authentication with Azure MySQL. The Service Principal credentials (tenant ID, client ID, and client secret)
* are securely stored in environment variables and accessed through the credential provider.
*
* @returns A promise that resolves when the function is complete.
*/
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
const credentialProvider = new ServicePrincipalCredentialProvider(tenantId, clientId, clientSecret);

const clientFactory = new MySQLClientFactory(
host,
user,
database,
port,
ssl,
credentialProvider
);

const client = await clientFactory.createClient();
await client.connect();
// Execute database operations
await client.end();
context.res = {
body: "Azure Function using Service Principal to access Azure MySQL"
};
};

export default httpTrigger;

Conclusion

You have successfully set up Azure MySQL using authentication in Azure Functions, with a modular architecture to facilitate maintenance.

Stackademic 🎓

Thank you for reading until the end. Before you go:


Connecting to Azure Database for MySQL with Azure Functions Using Native Drivers and Managed… was originally published in Stackademic on Medium, where people are continuing the conversation by highlighting and responding to this story.