DEV Community

Cover image for Media uploads to Azure blob storage: the REST API Way in Laravel/PHP.
Chidiebere Chukwudi
Chidiebere Chukwudi

Posted on • Edited on

Media uploads to Azure blob storage: the REST API Way in Laravel/PHP.

Hi there, I hope you're doing well. Firstly, this tutorial is somewhat Laravel-specific, but I assure you that you can apply the approach outlined here with vanilla PHP or any PHP framework you prefer.

Microsoft retired the PHP Azure Storage SDK, which means there will be no maintenance. So, if this is a concern for you, you may want to consider trying the REST API approach.

In this article, we are going to quickly highlight how you can upload images, videos or any media file to Azure using Azure Blob Storage REST API.

For sheer simplicity of a simple tutorial, let's just have a php method to put our logic and send a request to azure.

Like every normal PaaS out there, we will need some basic credentials to authorize access to the azure blob upload service.

Login in to your azure portal to get these credentials and also create a container then replace the placeholders in the following code with your credentials.

Typically, you want to protect these credentials by fetching them from your .env file. It's ideal not to hardcode your credentials.


public function uploadToAzureCloud($request)
    {
     $storageAccountName = config('services.azure_storage.account_name');
     $containerName = config('services.azure_storage.container');
    $accessKey = config('services.azure_storage.key');
}
Enter fullscreen mode Exit fullscreen mode

For the following code, we want to ensure that the request has a file. Then, we obtain the original filename, create a hashed variant for the filename that we are going to use as our blob name, and also obtain the file size.


// the previous code

if ($request->hasFile('file')) {

 $file = $request->file('file');
 $orignalFileName = $file->getClientOriginalName();
 $mimeType = $file->getMimeType();
 $blobName =  $file->hashName();

$fileSize = filesize($file->path());


//the upcoming code
}
Enter fullscreen mode Exit fullscreen mode

Constructing an Azure Blob Signature

Next up is constructing an azure blob signature.

A Shared Access Signature (SAS) is a security token that provides restricted access to certain resources in cloud-based storage services, such as Azure Blob storage or Azure File storage. SAS enables secure and controlled access to these resources without exposing the account's access keys or credentials.

A SaS token or signature is a string that azure demands that lets us make upload media files. The following code, generates SaS token.


$dateTime = gmdate('D, d M Y H:i:s \G\M\T');
 $urlResource = "/$storageAccountName/$containerName/{$blobName}";

$headerResource = "x-ms-blob-cache-control:max-age=3600\nx-ms-blob-type:BlockBlob\nx-ms-date:$dateTime\nx-ms-version:2019-12-12";

// Generate signature
$arraysign = [];                     // initiate an empty array (don't remove this 🙂)
$arraysign[] = 'PUT';               /*HTTP Verb*/
$arraysign[] = '';                  /*Content-Encoding*/
$arraysign[] = '';                  /*Content-Language*/
$arraysign[] = $fileSize;           /*Content-Length (include value when zero)*/
$arraysign[] = '';                  /*Content-MD5*/
$arraysign[] = $mimeType;         /*Content-Type*/
$arraysign[] = '';                  /*Date*/
$arraysign[] = '';                  /*If-Modified-Since */
$arraysign[] = '';                  /*If-Match*/
$arraysign[] = '';                  /*If-None-Match*/
$arraysign[] = '';                  /*If-Unmodified-Since*/
$arraysign[] = '';                  /*Range*/
$arraysign[] = $headerResource;     /*CanonicalizedHeaders*/
$arraysign[] = $urlResource;        /*CanonicalizedResource*/


// converts the array to a string as required by MS 
$str2sign = implode("\n", $arraysign);


$sig = base64_encode(hash_hmac('sha256', utf8_encode($str2sign), base64_decode($accessKey), true));


Enter fullscreen mode Exit fullscreen mode

Your major focus should be on the $sig variable that holds the final outcome of the sas token construct. Take note of the content-Type comment: you can adjust this field with your desired content type.Now with the SaS token signature covered, we can now make our REST API call.

Uploading media file

We are making use of guzzle for the API request.

The $url variable we will be making the request to is in this format:

"https://<AzureStorageName>.blob.core.windows.net/<containerName>/<blobName>";

Enter fullscreen mode Exit fullscreen mode

The HTTP verb to use is "PUT" followed by a header construct to pass certain header values like Authorization, etc.


// the previous code

$url = "https://$storageAccountName.blob.core.windows.net/$containerName/{$blobName}";


// use GuzzleHttp\Client;

$client = new Client();

$response = $client->request('PUT', $url, [
'headers' => [
   'Authorization' => $authHeader,
   'x-ms-blob-cache-control' => 'max-age=3600',
   'x-ms-blob-type' => 'BlockBlob',
   'x-ms-date' => $dateTime,
   'x-ms-version' => '2019-12-12',
   'Content-Type' => $mimeType,
   'Content-Length' => $fileSize
    ],
     'body' => fopen($file->path(), 'r'),
    ]);

//the next code up coming 

Enter fullscreen mode Exit fullscreen mode

Of course, we assigned a body to the put request. The body contains the actual file that we are trying to upload to azure.

Generating a url for our uploaded image.

If our upload is successful, Azure Blob Storage API sends a 200 response status code. If we confirm that our request is successful, we need to generate a media url to use in displaying images on the frontend.

To make sure we get a proper url that should work, we will generate a sas token from azure dashboard. This sas token is what will we use as our $mediaUrlSasToken . Follow the steps mentioned in this Azure guide to generate one

An example of interface to generate a Sas Token

An example of interface to generate a Sas Token

Once we have the the token, replace the token with the placeholder assigned to $urlSasToken

if ($response->getStatusCode() == 201) 
{
  // image sas token 
$urlSasToken = 'generated_sas_token';
 return response()->json(
   [
      'original_name' => $originalFIleName,
      'media_url' => "$url?$urlSasToken",
    ]
        );

    } else {
          return response() -> json (['message' => 'Something went wrong']);
       }
Enter fullscreen mode Exit fullscreen mode

Result

A successful request (201 status code) will look like this:

From

The full composite of the $mediaUrl is as follows:

"https://<AzureStorageName>.blob.core.windows.net/<containerName>/<blobName>?<urlSasToken>";

Enter fullscreen mode Exit fullscreen mode

Complete Code:


public function uploadToAzureCloud($request)
  {
     try { 
    $storageAccountName = config('services.azure_storage.account_name');
     $containerName = config('services.azure_storage.container');
    $accessKey = config('services.azure_storage.key');

    if ($request->hasFile('file')) {

     $file = $request->file('file');

     $orignalFileName = $file->getClientOriginalName();

     $mimeType = $file->getMimeType();

     $blobName =  'folder/'. $file->hashName();

     $fileSize = filesize($file->path());

     $dateTime = gmdate('D, d M Y H:i:s \G\M\T');

     $urlResource = "/$storageAccountName/$containerName/{$blobName}";

    $headerResource = "x-ms-blob-cache-control:max-age=3600\nx-ms-blob-type:BlockBlob\nx-ms-date:$dateTime\nx-ms-version:2019-12-12";

                // Generate signature
$arraysign = [];  // initiate an empty array (don't remove this 🙂)
$arraysign[] = 'PUT';               /*HTTP Verb*/
$arraysign[] = '';                  /*Content-Encoding*/
$arraysign[] = '';                  /*Content-Language*/
$arraysign[] = $fileSize;           /*Content-Length (include value when zero)*/
$arraysign[] = '';                  /*Content-MD5*/
$arraysign[] = $mimeType;         /*Content-Type*/
$arraysign[] = '';                  /*Date*/
$arraysign[] = '';                  /*If-Modified-Since */
$arraysign[] = '';                  /*If-Match*/
$arraysign[] = '';                  /*If-None-Match*/
$arraysign[] = '';                  /*If-Unmodified-Since*/
$arraysign[] = '';                  /*Range*/
$arraysign[] = $headerResource;     /*CanonicalizedHeaders*/
$arraysign[] = $urlResource;        /*CanonicalizedResource*/


  // converts the array to a string as required by MS 
    $str2sign = implode("\n", $arraysign);
    $sig = base64_encode(hash_hmac('sha256', utf8_encode($str2sign), base64_decode($accessKey), true));

$url = "https://$storageAccountName.blob.core.windows.net/$containerName/{$blobName}";


           // use GuzzleHttp\Client;

     $client = new Client();
$response = $client->request('PUT', $url, [
   'headers' => [
    'Authorization' => "SharedKey $storageAccountName:$sig",
    'x-ms-blob-cache-control' => 'max-age=3600',
    'x-ms-blob-type' => 'BlockBlob',
    'x-ms-date' => $dateTime,
    'x-ms-version' => '2019-12-12',
    'Content-Type' => $mimeType,
    'Content-Length' => $fileSize
    ],
       'body' => fopen($file->path(), 'r'),
     ]);          
  if ($response->getStatusCode() == 201) {
         // image sas token 
  $urlSasToken = config('services.azure_storage.sas_token');
    return
        [
          'original_name' =>   $orignalFileName,
          'media_url' => "$url?$urlSasToken",
         ];
        } else {
  return response()->json(['message' => 'Something went wrong']);
           }
       }
  } catch (RequestException $e) {
       // If there's an error, log the error message
    $errorMessage = $e->getMessage();
 return response()->json(['error' => $errorMessage], 500);
        }
    }
Enter fullscreen mode Exit fullscreen mode

Final Thoughts:

This is a REST API approach to uploading media and generating a media url to display the uploaded resource. You can organised your code properly into separate methods or have some blocks like .env configurations in a separate class or however you want.

Thanks for reading, I hope you do find this resource useful.

Find me on X(Formerly Twitter ) : jovial_core

Also, you can checkout what-company-stack 1.0 , an open source project we released earlier this year.

If you don't mind, there is a sponsorship button on my github profile 😊

Cover Photo by Sanat Anghan

Top comments (2)

Collapse
 
eaimiesylv profile image
eaimiesylv

This code implementation has saved me a lot of time. Excellent post

Collapse
 
jovialcore profile image
Chidiebere Chukwudi

Glad to help. I appreciate the feedback