DEV Community

Hazrat Ummar Shaikh
Hazrat Ummar Shaikh

Posted on Originally published at relayworks.dev on

Azure Blob Storage Direct Access IDE Plugin (Kotlin/Java)

Azure Blob Storage Direct Access IDE Plugin (Kotlin/Java)

Introduction: Why Bypass the SDK for Your IDE Plugin?

Executive Summary & Key Takeaways


  • Optimized IDE Plugin Development: Bypassing SDKs allows for a leaner dependency tree, resulting in faster compilation times and a more efficient user experience.
  • Direct Control Over API Interactions: Utilizing the Azure Blob Storage REST API directly provides developers with granular control over network requests, authentication, and error handling.
  • Custom Authorization Mechanism: Implementing Shared Key authorization enables precise control over authentication, enhancing security and functionality in server-side applications.
  • Tailored Solutions for Specific Use Cases: Developing a custom Azure Blob browser within an IDE can meet niche requirements without the overhead of general-purpose SDKs.

Building an integrated development environment (IDE) plugin often requires a delicate balance between functionality and footprint. When interacting with cloud services like Azure Blob Storage, the natural inclination is to use official Software Development Kits (SDKs). However, for specialized use cases—such as a lightweight, custom-built Azure Blob browser directly within your IDE—SDKs can introduce significant overhead. They often bundle extensive dependencies, abstract away low-level controls, and might include features irrelevant to your precise needs.

Bypassing the SDK allows developers control over network requests, authentication mechanisms, and error handling. This direct approach ensures a lean dependency tree, faster compilation times, and a highly optimized user experience within a resource-sensitive environment like an IDE. It's about crafting a solution that perfectly fits the niche, rather than adapting a general-purpose library. This article explains how to achieve precise, custom integration with Azure Blob Storage without the SDK, focusing on manual request signing with Kotlin/Java for a tailored developer experience. This method is ideal for IDE plugin Azure Blob integration without SDK, offering minimalism and efficiency.

Premium 3D isometric render, a minimalist IDE interface displaying code, with abstract glowing data streams bypassing a

Understanding the Azure Blob Storage REST API

Azure Blob Storage is a robust, massively scalable object storage solution, fundamentally exposed through a RESTful API. Every operation, from listing containers to uploading blobs, is an HTTP request to a specific endpoint. Understanding this underlying Azure Blob Storage REST API direct call is the key to interacting with it without an SDK. By sending standard HTTP requests, we gain direct control over headers, payloads, and authentication. This interaction is essential for highly customized applications or integration points where minimizing external dependencies is a priority. The official documentation for the Azure Blob Service REST API provides a comprehensive guide to these operations.

Auth Fundamentals: Authorizing with Shared Key

Accessing Azure Blob Storage directly necessitates proper authorization. The Shared Key authorization scheme is a powerful method that relies on signing your HTTP requests with your storage account's access key. This approach provides full access to the storage account and is essential for server-side applications or tools that require comprehensive control. The core principle involves constructing a canonicalized string from various parts of your HTTP request, then signing this string using HMAC-SHA256 with your storage account's primary or secondary access key. This signature is then included in the Authorization header of your request.

This manual signing process offers control over every aspect of authentication, making it a powerful technique for Manual Azure Blob Storage authentication Java and Kotlin applications. It ensures that only requests originating from an entity possessing the correct key can interact with your storage. While powerful, it also demands careful management of your storage account keys. The process is critical for any Azure Blob Storage HTTP request signing example and is documented in detail by Microsoft.

Architecture Diagram

The Signature String: What Goes In and Why

The canonicalized string is the heart of Shared Key authorization. It's a precisely formatted string that concatenates specific HTTP headers and request elements. The exact order and content are crucial, as any deviation will result in a signature mismatch and a 403 Forbidden response from Azure Storage. This string prevents tampering and ensures the request's authenticity.

The components generally include:

Component Description
HTTP Verb GET, PUT, HEAD, DELETE, etc.
Content-Encoding Value of the Content-Encoding header (empty if not present).
Content-Language Value of the Content-Language header (empty if not present).
Content-Length Value of the Content-Length header (or 0 if not present for GET/HEAD/DELETE).
Content-MD5 Value of the Content-MD5 header (empty if not present).
Content-Type Value of the Content-Type header (empty if not present).
Date Value of the Date header (or x-ms-date if used). This must be UTC.
If-Modified-Since Value of the If-Modified-Since header (empty if not present).
If-Match Value of the If-Match header (empty if not present).
If-None-Match Value of the If-None-Match header (empty if not present).
If-Unmodified-Since Value of the If-Unmodified-Since header (empty if not present).
Range Value of the Range header (empty if not present).
Canonicalized Headers All x-ms- headers, sorted by header name, lowercased, and values trimmed/concatenated.
Canonicalized Resource Storage account name, resource path, and canonicalized query parameters.

Each component is typically followed by a newline character (\n) in the signature string. The precise construction of the Canonicalized Resource is particularly complex, involving the storage account name, the resource path (e.g., /myaccount/mycontainer/myblob), and any comp or res query parameters. This detail-oriented process is essential to Generate Azure Blob Shared Key signature correctly.

Crafting Your HMAC-SHA256 Signature in Kotlin/Java

Generating the HMAC-SHA256 signature involves using cryptographic utilities available in both Kotlin and Java. The process entails converting your storage account key from Base64 to bytes, then using it to sign the UTF-8 encoded canonicalized string. This results in a byte array, which must then be Base64 encoded again to be placed in the Authorization header. This snippet serves as a core component for Kotlin Azure Blob Storage custom client or its Java counterpart.

Here's a Kotlin example for generating the signature string:


import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.nio.charset.StandardCharsets
import java.util.Base64

fun generateSharedKeyLiteSignature(
    stringToSign: String,
    accountKey: String
): String {
    try {
        val decodedAccountKey = Base64.getDecoder().decode(accountKey)
        val hmacSha256 = Mac.getInstance("HmacSHA256")
        val secretKey = SecretKeySpec(decodedAccountKey, "HmacSHA256")
        hmacSha256.init(secretKey)

        val signedBytes = hmacSha256.doFinal(stringToSign.toByteArray(StandardCharsets.UTF_8))
        return Base64.getEncoder().encodeToString(signedBytes)
    } catch (e: Exception) {
        throw RuntimeException("Error generating Azure Blob Storage signature", e)
    }
}

// Example Usage (for listing blobs in a container):
/*
fun main() {
    val accountName = "YOUR_STORAGE_ACCOUNT_NAME"
    val accountKey = "YOUR_STORAGE_ACCOUNT_KEY" // Base64 encoded

    val httpVerb = "GET"
    val contentEncoding = ""
    val contentLanguage = ""
    val contentLength = "" // For GET/HEAD/DELETE, typically empty or 0 if no body
    val contentMd5 = ""
    val contentType = ""
    val date = "Mon, 27 Dec 2023 12:00:00 GMT" // UTC date, crucial for signature
    val ifModifiedSince = ""
    val ifMatch = ""
    val ifNoneMatch = ""
    val ifUnmodifiedSince = ""
    val range = ""

    // x-ms-date header for modern APIs, preferred over general Date header
    val xMsDate = date // Use the same date string for x-ms-date
    val xMsVersion = "2023-01-03" // Azure Storage API version

    // Canonicalized headers (sorted, lowercase, x-ms- prefix)
    val canonicalizedHeaders = "x-ms-date:$xMsDate\nx-ms-version:$xMsVersion"

    // Canonicalized resource (account name + path + query parameters)
    val canonicalizedResource = "/$accountName/mycontainer" // Example: listing blobs in 'mycontainer'
    // If you were listing containers, it would be just "/$accountName/"

    val stringToSign = "$httpVerb\n" +
            "$contentEncoding\n" +
            "$contentLanguage\n" +
            "$contentLength\n" +
            "$contentMd5\n" +
            "$contentType\n" +
            "$date\n" + // Note: if using x-ms-date, this should be empty. But Azure docs show it here.
                       // For Shared Key Lite, the Date header is still part of the string.
                       // For Shared Key, the x-ms-date header replaces the Date header.
                       // This example follows Shared Key Lite for simplicity, usually x-ms-date is preferred.
                       // Let's stick to the recommendation to use x-ms-date and leave 'Date' blank in stringToSign.
            "$ifModifiedSince\n" +
            "$ifMatch\n" +
            "$ifNoneMatch\n" +
            "$ifUnmodifiedSince\n" +
            "$range\n" +
            "$canonicalizedHeaders\n" +
            "$canonicalizedResource"

    // Corrected stringToSign for Shared Key (not Lite), using x-ms-date and empty Date header
    val actualStringToSign = "$httpVerb\n" +
            "$contentEncoding\n" +
            "$contentLanguage\n" +
            "$contentLength\n" +
            "$contentMd5\n" +
            "$contentType\n" +
            "\n" + // Empty Date header, as x-ms-date is canonicalized separately
            "$ifModifiedSince\n" +
            "$ifMatch\n" +
            "$ifNoneMatch\n" +
            "$ifUnmodifiedSince\n" +
            "$range\n" +
            "$canonicalizedHeaders\n" +
            "$canonicalizedResource"

    val signature = generateSharedKeyLiteSignature(actualStringToSign, accountKey)
    println("Signature: $signature")
    println("Authorization Header: SharedKey $accountName:$signature")
}
*/

This Azure Blob Storage HTTP request signing example demonstrates the core cryptographic operation. The stringToSign construction is the most critical and often error-prone part; double-check Azure's official documentation for the exact format based on the API version and authorization scheme (Shared Key vs. Shared Key Lite).

Executing the Request: An HTTP Client Example

With the signature generated, the next step is to assemble and send the HTTP request. We can use Java's built-in java.net.http.HttpClient for this, which provides a modern, asynchronous way to make HTTP requests. This example demonstrates how to perform a simple GET request to list blobs within a specific container, showcasing direct access to Azure Blob from custom app.


import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.time.ZoneOffset
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.util.Locale

// (Assume generateSharedKeyLiteSignature from previous section is available)

fun listBlobsInContainer(
    accountName: String,
    accountKey: String,
    containerName: String
): String? {
    val httpClient = HttpClient.newBuilder().build()
    val azureApiVersion = "2023-01-03" // Consistent API version

    // Date for x-ms-date header and string to sign
    val now = ZonedDateTime.now(ZoneOffset.UTC)
    val dateHeaderValue = now.format(DateTimeFormatter.RFC_1123_DATE_TIME.withLocale(Locale.ENGLISH))

    // Construct the canonicalized string
    val httpVerb = "GET"
    val contentEncoding = ""
    val contentLanguage = ""
    val contentLength = "" // For GET, empty
    val contentMd5 = ""
    val contentType = ""
    val date = "" // Empty, as x-ms-date is preferred and canonicalized separately
    val ifModifiedSince = ""
    val ifMatch = ""
    val ifNoneMatch = ""
    val ifUnmodifiedSince = ""
    val range = ""

    val canonicalizedHeaders = "x-ms-date:$dateHeaderValue\nx-ms-version:$azureApiVersion"
    val canonicalizedResource = "/$accountName/$containerName?restype=container&comp=list" // List blobs in container

    val stringToSign = "$httpVerb\n" +
            "$contentEncoding\n" +
            "$contentLanguage\n" +
            "$contentLength\n" +
            "$contentMd5\n" +
            "$contentType\n" +
            "$date\n" +
            "$ifModifiedSince\n" +
            "$ifMatch\n" +
            "$ifNoneMatch\n" +
            "$ifUnmodifiedSince\n" +
            "$range\n" +
            "$canonicalizedHeaders\n" +
            "$canonicalizedResource"

    val signature = generateSharedKeyLiteSignature(stringToSign, accountKey)
    val authorizationHeader = "SharedKey $accountName:$signature"

    val uri = URI("https://$accountName.blob.core.windows.net/$containerName?restype=container&comp=list")

    val request = HttpRequest.newBuilder()
        .uri(uri)
        .header("x-ms-date", dateHeaderValue)
        .header("x-ms-version", azureApiVersion)
        .header("Authorization", authorizationHeader)
        .GET()
        .build()

    return try {
        val response = httpClient.send(request, HttpResponse.BodyHandlers.ofString())
        if (response.statusCode() == 200) {
            response.body()
        } else {
            println("Error: ${response.statusCode()} - ${response.body()}")
            null
        }
    } catch (e: Exception) {
        println("Request failed: ${e.message}")
        null
    }
}

/*
fun main() {
    val accountName = "YOUR_STORAGE_ACCOUNT_NAME"
    val accountKey = "YOUR_STORAGE_ACCOUNT_KEY" // Base64 encoded
    val containerName = "yourcontainer"

    val blobListXml = listBlobsInContainer(accountName, accountKey, containerName)
    if (blobListXml != null) {
        println("Blobs in container '$containerName':\n$blobListXml")
    } else {
        println("Failed to retrieve blob list.")
    }
}
*/

This example provides a concrete foundation for building direct access to Azure Blob from custom app that can perform various operations by adjusting the HTTP verb, URI, and body as needed. The XML response body can then be parsed to display blob information within your IDE plugin.

Advanced Authorization: Service SAS Tokens (Briefly)

While Shared Key authorization grants full control, it also carries the risk of key exposure. For scenarios requiring more granular permissions or temporary access, Service SAS (Shared Access Signature) tokens are a superior alternative. A Service SAS is a URI that grants restricted access rights to your Azure Storage resources for a specified period. The SAS token itself is appended to the resource URI and includes parameters defining permissions, start/expiry times, IP restrictions, and the signature itself. Generating a Service SAS still typically requires the storage account's Shared Key on the server-side to sign the SAS string, or Azure AD authorization. For a client-side application like an IDE plugin, it’s safer to consume pre-generated SAS tokens or have a backend service generate them on demand. The official documentation on Azure Blob SAS token generation manual provides the full details.

Security Implications & Best Practices

Directly handling Azure Storage account keys demands stringent security practices. Unlike SDKs that might integrate with managed identity or Azure AD for token-based authentication, manual Shared Key signing directly exposes your primary account keys within your application's logic.

Key considerations:

  • Key Management: Never hardcode storage account keys. Use secure environment variables, a dedicated secrets management service (e.g., Azure Key Vault, HashiCorp Vault), or a secure configuration system.
  • Least Privilege: If possible, generate and use SAS tokens with the absolute minimum necessary permissions and shortest possible expiry times, rather than constantly using the full Shared Key. This mitigates the impact if a credential is compromised.
  • HTTPS Only: Always enforce HTTPS for all requests to Azure Storage. This encrypts traffic, protecting your signed requests and data from eavesdropping.
  • Avoid Client-Side Key Exposure: For browser-based or purely client-side applications, directly using Shared Key authorization is a severe security risk. A backend proxy should handle signing requests. For IDE plugins, consider the plugin's distribution model and where the key resides. If the plugin is installed on a developer's machine, ensure the key is stored securely (e.g., OS-level credential manager) and never distributed with the plugin binaries.
  • Auditing and Monitoring: Monitor Azure Storage access logs for unusual activity, especially concerning requests authorized by Shared Key.

Adhering to these practices is crucial to maintain the integrity and confidentiality of your Azure Storage resources when pursuing direct integration.

Premium 3D isometric render, a glowing digital security shield protecting a cluster of holographic data points, surround

SDK vs. Direct: Weighing the Trade-offs

Choosing between using an official SDK and direct REST API interaction involves evaluating several factors based on your project's specific requirements. For a custom IDE plugin, the choice often leans towards direct interaction for the control it offers.

Feature Official SDK Direct REST API (Manual Signing)
Ease of Use High (abstracts complexity) Low (requires deep understanding)
Control & Flexibility Moderate (bound by SDK's design) High (granular control over every aspect)
Dependencies High (adds external libraries) Low (uses standard HTTP client and crypto)
Learning Curve Moderate (learn SDK's API) High (understand REST API, auth protocols)
Maintenance Overhead Moderate (SDK updates, dependency management) Moderate (manual protocol updates, error handling)
Footprint Larger (more code, dependencies) Smaller (minimalist, only what's needed)
Authentication Options Broader (Azure AD, Managed Identity, SAS, Shared Key) Focus on Shared Key/SAS (manual implementation)

For projects prioritizing a lightweight, highly specific solution without external dependencies, direct REST API interaction is often the preferred path. This is especially true when building tools like an IDE plugin where performance, startup time, and resource consumption are paramount.

Integrating Your Custom Client into an IDE Plugin

Integrating your custom Azure Blob Storage client into an IDE plugin transforms the development experience, offering a native feel for cloud resource management. The custom client, built on the principles discussed, becomes a core utility within your plugin's architecture.

The process typically involves:

  1. Service Layer: Encapsulate the HTTP request generation, signature crafting, and response parsing logic within a dedicated service or repository layer within your plugin. This separates concerns from the UI.
  2. Configuration Management: Provide a secure way for users to input their Azure Storage account name and key (or SAS token) within the IDE settings. Store these credentials securely using the IDE's built-in secrets management or OS-level credential stores, not plaintext.
  3. UI Integration: Develop UI components (e.g., tool windows, tree views) that leverage your custom client to display containers, list blobs, and enable operations like upload, download, or deletion.
  4. Error Handling and Feedback: Implement robust error handling to gracefully manage API failures, network issues, and authentication errors, providing clear feedback to the developer.
  5. Asynchronous Operations: Ensure all network operations are performed asynchronously to prevent freezing the IDE's UI thread. Kotlin coroutines or Java's CompletableFuture are excellent for this.

This integrated approach offers a workflow for developers. For example, a developer could right-click a project file and upload it directly to a pre-configured Azure Blob Storage container via your plugin, or browse existing blobs without leaving their development environment.

Architecture Diagram

Conclusion: Tailored Access, Unparalleled Control

Building a custom Azure Blob Storage client for your IDE plugin using manual request signing offers a pathway to control and efficiency. By bypassing the traditional SDK, you gain the freedom to craft a minimalist, highly optimized solution perfectly tailored to your development workflow. While it demands a deeper understanding of the Azure Blob Storage REST API and cryptographic signing, the benefits in terms of reduced dependencies, smaller footprint, and fine-grained control are significant for specialized tools. This approach empowers developers to integrate cloud storage into their daily work without unnecessary overhead.

Whether you're looking to streamline your cloud development processes or need a bespoke integration, direct API interaction offers a robust solution. If you're tackling complex integration challenges or need custom automation, consider the expertise RelayWorks provides. From building RelayWorks Custom Bot Development to intricate cloud integrations, our team is equipped to deliver tailored solutions that meet your exact needs. Feel free to explore our services or Contact RelayWorks to discuss your next project.

Top comments (0)