Media uploads to Azure blob storage: the REST API Way in Laravel/PHP.
Tags: azure, php, laravel, restapi
Published: 2024-03-23 14:37:21

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](https://github.com/Azure/azure-sdk-for-php?tab=readme-ov-file#microsoft-azure-sdk-for-php) 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] (https://learn.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage?tabs=azure-portal#view-account-access-keys) and also [create a container](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-portal#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.

```php

public function uploadToAzureCloud($request)
    {
     $storageAccountName = config('services.azure_storage.account_name');
     $containerName = config('services.azure_storage.container');
    $accessKey = config('services.azure_storage.key');
}
```
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.

```php

// the previous code

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

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

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


//the upcoming code
}
```
#### 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. 

```php

$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[] = 'image/png';         /*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));
                

``` 
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: 

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

```

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

```php

// 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' => 'image/png',
   'Content-Length' => $fileSize
    ],
     'body' => fopen($file->path(), 'r'),
    ]);

//the next code up coming 

``` 
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] (https://learn.microsoft.com/en-us/azure/data-explorer/kusto/api/connection-strings/generate-sas-token#azure-portal)


An example of interface to generate a Sas Token 

![An example of interface to generate a Sas Token](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h76jpvqsacvpppp4cdyk.png)



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

```php
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']);
       }
```

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


![From ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5hn87elvwqkgzysua4pk.jpeg)

The full composite of the `$mediaUrl` is as follows: 
```php
"https://<AzureStorageName>.blob.core.windows.net/<containerName>/<blobName>?<urlSasToken>";

```

#### Complete Code:
```php

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();

     $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[] = 'image/png';         /*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' => 'image/png',
    '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);
        }
    }
```
#### 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](https://twitter.com/jovial_core)

Also, you can checkout [what-company-stack 1.0 ](https://github.com/jovialcore), an open source project we released earlier this year. 

If you don't mind, there is a sponsorship button on [ my github profile ] (https://github.com/sponsors/jovialcore) 😊

[Cover Photo by Sanat Anghan](https://www.pexels.com/photo/motor-boat-making-circle-on-water-surface-6785289/
)