Have any questions:

Toll free:9801887718Available 24/7

Email our experts:info@mantraideas.com

In: Laravel

Onedrive is one of the most popular and affordable cloud storage platforms businesses use for storing their files. This tutorial will cover integrating onedrive in your laravel application and assume you have basic knowledge of laravel and may skip some minor details. You can also access our example repository here (https://github.com/MantraIdeas/laravel-onedrive-integration)  to get access to the full code. This example uses the service pattern to separate logic and keep the code clean.

Step 1: Setting up a new project

First let us start with creating a new project in laravel. Assuming you have the laravel cli installed.

laravel new onedrive-integration

The installer will prompt for your preferred config like starter kit and database. Select any that fit your needs. Provide appropriate database credentials in your .env file and run the migrations, now we have a fresh laravel project.

Step 2: Handling authentication

Onedrive uses OAuth 2.0 flow for handling authentication. There are multiple options on how to authenticate your app with onedrive. In our example we are going to implement our own refresh token and access token fetching logic so our application is not dependent on other packages.

First in your web.php file create a controller named OnedriveController and create the following routes.

<?php

use App\Http\Controllers\OnedriveController;
use Illuminate\Support\Facades\Route;

Route::get('/',[OnedriveController::class,'home'])->name('home');
Route::get('/onedrive', [OnedriveController::class, 'initiateOnedriveAuth']);
Route::get('/onedrive-auth-callback', [OnedriveController::class, 'storeOneDriveAuth']);
Route::post('/store-file',[OnedriveController::class,'storeFile'])->name('store-file');

And inside the Controller we have the following code

<?php

namespace App\Http\Controllers;

use App\Http\Requests\StoreFileRequest;
use App\Models\OnedriveFile;
use App\Services\Onedrive\Auth\GetAuthUrlService;
use App\Services\Onedrive\Auth\StoreAuthCredService;
use App\Services\Onedrive\FileUpload\UploadFileService;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

readonly class OnedriveController
{
    public function __construct(
        private GetAuthUrlService    $getAuthUrlService,
        private StoreAuthCredService $storeAuthCredService,
        private UploadFileService $uploadFileService,
    )
    {
    }
    public function home(){
        $files = OnedriveFile::all();
        return view('welcome',compact('files'));
    }

    /**
     * @throws Exception
     */
    public function initiateOneDriveAuth()
    {
        return redirect($this->getAuthUrlService->getAuthUrl());
    }

    /**
     * @throws Exception
     */
    public function storeOneDriveAuth(Request $request)
    {
        if(!$request->get('code')){
            abort(403,"Code not found");
        }
        try{
            $this->storeAuthCredService->getAndStoreAccessToken($request->get('code'));
        }catch (Exception $e) {
            Log::critical("Onedrive error: ".$e->getMessage());
            Log::critical($e->getTraceAsString());
            throw new Exception('Could not obtain access token');
        }
        echo "<h1>Onedrive connection successful</h1>";
    }
    public function storeFile(StoreFileRequest $request){
        try{
            $uploadedFile = $request->file('file');
            $fileName = $uploadedFile->getClientOriginalName();
            // We are going to store it in the test folder.
            $filePathWithName = '/test/' . $fileName;
            $response = $this->uploadFileService->upload($uploadedFile,$filePathWithName);
            OnedriveFile::create([
                'file_id' => $response['id'],
                'file_name' => $response['name'],
                'mime_type' => $response['file']['mimeType'],
                'folder_id' => $response['parentReference']['id'],
                'parent_folder_id' => $response['parentReference']['id'],
                'web_url' => $response['webUrl'],
                'download_url' => $response['@microsoft.graph.downloadUrl'],
            ]);
            return redirect()->route('home')->with('message', 'File uploaded to onedrive successfully');
        }catch (Exception $exception){
            Log::critical('Failed to store file.');
            Log::critical($exception->getMessage());
            Log::critical($exception->getTraceAsString());
            return redirect()->route('home')->with('message', 'File upload failed');
        }
    }
}

```

This controller holds a lot of logic so let us tackle it one by one. First let us look at the initiateOneDriveAuth function. It uses the following services and files

<?php

namespace App\Services\Onedrive\Auth;

use App\Services\Onedrive\Constant\OnedrivePermissionsConstant;
use App\Transport\Onedrive\Auth\AuthClient;

class GetAuthUrlService {
    public function __construct(
        private AuthClient $authClient,
    )
    {
    }
    public function getAuthUrl(): string
    {
        return $this->authClient->getLogInUrl(OnedrivePermissionsConstant::PERMISSIONS, config('onedrive.ONEDRIVE_REDIRECT_URI'));
    }
}
<?php
namespace App\Transport\Onedrive\Auth;

use App\Services\Onedrive\Constant\OnedriveUrlConstant;
use App\Services\Onedrive\State\GetOnedriveStateService;
use Exception;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Http;

class AuthClient {
    public function __construct(
        private GetOnedriveStateService $getOnedriveStateService,
    )
    {
    }
    public function getLogInUrl(array $scopes, string $redirectUri) : string {
        $values = [
            'client_id'     => config('onedrive.ONEDRIVE_CLIENT_ID'),
            'response_type' => 'code',
            'redirect_uri'  => $redirectUri,
            'scope'         => implode(' ', $scopes),
            'response_mode' => 'query',
        ];

        $query = http_build_query($values, '', '&', PHP_QUERY_RFC3986);
        return OnedriveUrlConstant::AUTH_URL . "?$query";
    }

    /**
     * @throws ConnectionException
     * @throws Exception
     */
    public function getAndStoreAccessToken(string $code) : void {
        $values = [
            'client_id'     => config('onedrive.ONEDRIVE_CLIENT_ID'),
            'redirect_uri'  => config('onedrive.ONEDRIVE_REDIRECT_URI'),
            'client_secret' => config('onedrive.ONEDRIVE_CLIENT_SECRET'),
            'code'          => (string) $code,
            'grant_type'    => 'authorization_code',
        ];

        $response = Http::asForm()->post(
            OnedriveUrlConstant::TOKEN_URL,
            $values
        );

        $body = (string) $response->getBody();
        $data = json_decode($body);

        if ($data === null) {
            throw new Exception('json_decode() failed');
        }
        $oneDriveState = $this->getOnedriveStateService->getOnedriveState();
        $oneDriveState->token = $data->access_token;
        $oneDriveState->token_obtained_time = time();
        $oneDriveState->refresh_token = $data->refresh_token;
        $oneDriveState->expires_in = $data->expires_in;
        $oneDriveState->save();
    }
}
<?php

namespace App\Services\Onedrive\Constant;

class OnedrivePermissionsConstant {
    public const PERMISSIONS = [
        'files.read',
        'files.read.all',
        'files.readwrite',
        'files.readwrite.all',
        'offline_access',
    ];
}
<?php

return [
    /**
     * Your OneDrive client ID.
     */
    'ONEDRIVE_CLIENT_ID' => env('ONEDRIVE_CLIENT_ID'),

    /**
     * Your OneDrive client secret.
     */
    'ONEDRIVE_CLIENT_SECRET' => env('ONEDRIVE_CLIENT_SECRET'),

    /**
     * Your OneDrive redirect URI.
     */
    'ONEDRIVE_REDIRECT_URI' => env('ONEDRIVE_REDIRECT_URI'),
    'ONEDRIVE_TENANT_ID' => env('ONEDRIVE_TENANT_ID'),
];

#ONEDRIVE
ONEDRIVE_CLIENT_ID=
ONEDRIVE_CLIENT_SECRET=
ONEDRIVE_TENANT_ID=
ONEDRIVE_REDIRECT_URI=http://localhost:8000/onedrive-auth-callback

Onedrive requires us to provide the above env variables. You need to put your client_id, client_secret, tenant_id as given by onedrive in microsoft azure dashboard and the ONEDRIVE_REDIRECT_URI must be the route onedrive-auth-callback route. In the azure dashboard you also need to set the redirect uri as http://localhost:8000/onedrive-auth-callback so onedrive knows where to send the authentication code. Make sure to run the following code to cache your env variables so we can read them from the config global helper.

<?php

namespace App\Services\Onedrive\State;

use App\Models\OnedriveState;

class GetOnedriveStateService {
    public function __construct()
    {
    }
    public function getOnedriveState(): OnedriveState {
        $onedriveState = OnedriveState::latest()->first();
        if(empty($onedriveState)) {
            $onedriveState = new OnedriveState();
            $onedriveState->save();
        }
        return $onedriveState;
    }
}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class OnedriveState extends Model
{
    protected $guarded = ['id'];
}
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('onedrive_states', function (Blueprint $table) {
            $table->id();
            $table->text('token')->collation('utf8mb4_unicode_ci')->nullable();
            $table->text('refresh_token')->collation('utf8mb4_unicode_ci')->nullable();
            $table->integer('token_obtained_time')->nullable();
            $table->integer('expires_in')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('onedrive_states');
    }
};
<?php
namespace App\Services\Onedrive\Constant;

class OnedriveUrlConstant {
    const AUTH_URL = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize';
    const TOKEN_URL = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
}

Here let’s start with the getLoginUrl funcion of AuthClient class. Here we are forming a url with permissions (or scopes our app will be able to have access to), client_id, and the redirect uri as input and the function will give us a url which we will redirect the user to. Go to /onedrive route and this will take the user to the onedrive login page and ask the user to login and give consent to our app to access the user’s onedrive. After the user has successfully logged in onedrive will perform a get request to our redirect url and give us a code in the code query param. We can use this code to request an access token and a refresh token and store it in our database. Now let us have a deeper look into the storeOneDriveAuth function of Onedrive Controller. 
The function is composed of following files and services.

// in onedrive controller
 /**
     * @throws Exception
     */
    public function storeOneDriveAuth(Request $request)
    {
        if(!$request->get('code')){
            abort(403,"Code not found");
        }
        try{
            $this->storeAuthCredService->getAndStoreAccessToken($request->get('code'));
        }catch (Exception $e) {
            Log::critical("Onedrive error: ".$e->getMessage());
            Log::critical($e->getTraceAsString());
            throw new Exception('Could not obtain access token');
        }
        echo "<h1>Onedrive connection successful</h1>";
    }

The function itself, which is inside the Onedrive Controller. 

<?php

namespace App\Services\Onedrive\Auth;

use App\Transport\Onedrive\Auth\AuthClient;
use Illuminate\Http\Client\ConnectionException;

class StoreAuthCredService {
    public function __construct(
        private AuthClient $authClient,
    )
    {
    }

    /**
     * @throws ConnectionException
     */
    public function getAndStoreAccessToken(string $code): void
    {
        $this->authClient->getAndStoreAccessToken($code);
    }
}

The StoreAuthCredService which calls the AuthClient’s getAndStoreAccessToken function

// inside AuthClient
 /**
     * @throws ConnectionException
     * @throws Exception
     */
    public function getAndStoreAccessToken(string $code) : void {
        $values = [
            'client_id'     => config('onedrive.ONEDRIVE_CLIENT_ID'),
            'redirect_uri'  => config('onedrive.ONEDRIVE_REDIRECT_URI'),
            'client_secret' => config('onedrive.ONEDRIVE_CLIENT_SECRET'),
            'code'          => (string) $code,
            'grant_type'    => 'authorization_code',
        ];

        $response = Http::asForm()->post(
            OnedriveUrlConstant::TOKEN_URL,
            $values
        );

        $body = (string) $response->getBody();
        $data = json_decode($body);

        if ($data === null) {
            throw new Exception('json_decode() failed');
        }
        $oneDriveState = $this->getOnedriveStateService->getOnedriveState();
        $oneDriveState->token = $data->access_token;
        $oneDriveState->token_obtained_time = time();
        $oneDriveState->refresh_token = $data->refresh_token;
        $oneDriveState->expires_in = $data->expires_in;
        $oneDriveState->save();
    }

The storeOneDriveAuth will get the code from the request and pass it into the StoreAuthCredService which will pass it to the AuthClient. Here we will use our client_id, redirect uri, client secret and our code with ‘authorization_code’ as grant type to get our access and refresh token data and store it in the database. Please refer to OnedriveState.php and the migration file for storing Onedrive State. We will use the database as a persistent state to store our access tokens and refresh tokens so we can access it any time for our onedrive api.  Now that we have the access token and refresh token we need to make sure the access token does not expire when we hit onedrive’s file apis. The access token has a time to live or expiry time or 1 hour after which we will need to use the refresh token to generate a new access token. The following service will handle retrieval of the access token and make sure the token has not expired. If the access token has expired then the service should use the refresh token to generate a new access token and store it in our database.

<?php

namespace App\Services\Onedrive\Auth;


use App\Models\OnedriveState;
use App\Services\Onedrive\Constant\AccessTokenStatusConstant;
use App\Services\Onedrive\State\GetOnedriveStateService;
use Exception;
use Illuminate\Http\Client\ConnectionException;

class AccessTokenService
{
    private OnedriveState $onedriveState;

    public function __construct(
        private readonly GetOnedriveStateService $getOnedriveStateService,
        private readonly RenewAccessTokenService $renewAccessTokenService,
    )
    {
        $this->onedriveState = $this->getOnedriveStateService->getOnedriveState();
    }

    /**
     * @throws ConnectionException
     * @throws Exception
     */
    public function getAccessToken():string
    {
        if ($this->onedriveState->refresh_token === null) {
            throw new Exception('Missing Refresh Token');
        }
        if (
            $this->getAccessTokenStatus() === AccessTokenStatusConstant::EXPIRING ||
            $this->getAccessTokenStatus() === AccessTokenStatusConstant::EXPIRED
        ) {
            $this->renewAccessTokenService->renewAccessToken();
            // fetch the modal again from the db since access token is changed.
            $this->onedriveState->refresh();
        }
        return $this->onedriveState->token;
    }

    public function getRefreshToken(): ?string
    {
        return $this->onedriveState->refresh_token;
    }

    public function getAccessTokenStatus(): int
    {
        if ($this->onedriveState->token === null) {
            return AccessTokenStatusConstant::MISSING;
        }

        $remaining = $this->getTokenExpire();

        if ($remaining <= 0) {
            return AccessTokenStatusConstant::EXPIRED;
        }

        if ($remaining <= 60) {
            return AccessTokenStatusConstant::EXPIRING;
        }

        return AccessTokenStatusConstant::VALID;
    }

    /**
     * Gets the access token expiration delay in seconds.
     *
     */
    public function getTokenExpire()
    {
        return $this->onedriveState->token_obtained_time
            + $this->onedriveState->expires_in - time();
    }
}
<?php

namespace App\Services\Onedrive\Auth;

use App\Models\OnedriveState;
use App\Services\Onedrive\State\GetOnedriveStateService;
use App\Transport\Onedrive\Auth\RenewTokenClient;
use Illuminate\Http\Client\ConnectionException;

class RenewAccessTokenService {
    private OnedriveState $onedriveState;
    public function __construct(
        private GetOnedriveStateService $getOnedriveStateService,
        private RenewTokenClient $renewTokenClient,
    )
    {
        $this->onedriveState = $this->getOnedriveStateService->getOnedriveState();
    }

    /**
     * @throws ConnectionException
     */
    public function renewAccessToken(): void
    {
        $this->renewTokenClient->renewAccessToken($this->onedriveState);
    }
}

<?php

namespace App\Transport\Onedrive\Auth;

use App\Models\OnedriveState;
use App\Services\Onedrive\Constant\OnedrivePermissionsConstant;
use App\Services\Onedrive\Constant\OnedriveUrlConstant;
use Exception;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Http;

class RenewTokenClient
{
    public function __construct()
    {
    }

    /**
     * @throws ConnectionException
     * @throws Exception
     */
    public function renewAccessToken(OnedriveState $oneDriveState): void
    {
        if ($oneDriveState->refresh_token === null) {
            throw new Exception(
                'The refresh token is not set or no permission for'
                . ' \'offline_access\' was given to renew the token'
            );
        }

        $scopes = OnedrivePermissionsConstant::PERMISSIONS;
        $values = [
            'client_id' => config('onedrive.ONEDRIVE_CLIENT_ID'),
            'client_secret' => config('onedrive.ONEDRIVE_CLIENT_SECRET'),
            'grant_type' => 'refresh_token',
            'scope'  => implode(' ', $scopes),
            'refresh_token' => $oneDriveState->refresh_token,
        ];

        $response = Http::asForm()->post(
            OnedriveUrlConstant::TOKEN_URL,
            $values
        );

        $body = (string)$response->getBody();
        $data = json_decode($body);

        if ($data === null) {
            throw new Exception('json_decode() failed');
        }

        $oneDriveState->token = $data->access_token;
        $oneDriveState->token_obtained_time = time();
        $oneDriveState->refresh_token = $data->refresh_token;
        $oneDriveState->expires_in = $data->expires_in;
        $oneDriveState->save();
    }
}

Step3: File Upload

Now let us proceed to actual file uploads by focusing on the /home route. Which will return the following blade file. First create a OnedriveFile.php modal and a migration for storing onedrive files

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class OnedriveFile extends Model
{
    protected  $guarded =  ['id'];
}
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('onedrive_files', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('file_id')->nullable();
            $table->string('parent_folder_id')->nullable();
            $table->string('folder_id')->nullable();
            $table->string('file_name')->nullable();
            $table->string('mime_type')->nullable();
            $table->timestamp('created_at')->nullable();
            $table->timestamp('updated_at')->nullable();
            $table->string('web_url')->nullable();
            $table->text('download_url');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('onedrive_files');
    }
};

And the welcome.blade view which will be rendered for displaying files and making post requests.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>OneDrive File Upload</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f4f6f8;
            display: flex;
            align-items: center;
            justify-content: center;
            height: 100vh;
            margin: 0;
        }

        .upload-container {
            background-color: white;
            padding: 2rem;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
            width: 300px;
            text-align: center;
        }

        h2 {
            margin-bottom: 1.5rem;
            color: #333;
        }

        input[type="file"] {
            display: block;
            margin: 1rem auto;
        }

        button {
            background-color: #0078D4;
            color: white;
            border: none;
            padding: 0.6rem 1.2rem;
            border-radius: 4px;
            cursor: pointer;
            font-size: 1rem;
        }

        button:hover {
            background-color: #005ea6;
        }
    </style>
</head>
<body>

<div class="upload-container">
    <h2>Upload to OneDrive</h2>
    <form id="uploadForm" method="post" action="{{route('store-file')}}" enctype="multipart/form-data">
        @csrf
        <input type="file" name="file" required>
        <button type="submit">Upload</button>
        @if (session('message'))
            <div style="padding-top: 10px">
                {{ session('message') }}
            </div>
        @endif
        <br>
        <div>
            @if($files->count() > 0)
                <br>
                <hr>
                <h2>Uploaded files</h2>
                @foreach($files as $file)
                    <ul>
                        <li><a href="{{$file->download_url}}">{{$file->file_name}}</a></li>
                    </ul>
                @endforeach
            @endif
        </div>
    </form>
</div>

</body>
</html>
// Inside Onedrive Controller
 public function storeFile(StoreFileRequest $request){
        try{
            $uploadedFile = $request->file('file');
            $fileName = $uploadedFile->getClientOriginalName();
            // We are going to store it in the test folder.
            $filePathWithName = '/test/' . $fileName;
            $response = $this->uploadFileService->upload($uploadedFile,$filePathWithName);
            OnedriveFile::create([
                'file_id' => $response['id'],
                'file_name' => $response['name'],
                'mime_type' => $response['file']['mimeType'],
                'folder_id' => $response['parentReference']['id'],
                'parent_folder_id' => $response['parentReference']['id'],
                'web_url' => $response['webUrl'],
                'download_url' => $response['@microsoft.graph.downloadUrl'],
            ]);
            return redirect()->route('home')->with('message', 'File uploaded to onedrive successfully');
        }catch (Exception $exception){
            Log::critical('Failed to store file.');
            Log::critical($exception->getMessage());
            Log::critical($exception->getTraceAsString());
            return redirect()->route('home')->with('message', 'File upload failed');
        }
    }
<?php

namespace App\Services\Onedrive\FileUpload;


use App\Services\Onedrive\Auth\AccessTokenService;
use Exception;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

readonly class UploadFileService
{
    public function __construct(
        private AccessTokenService         $accessTokenService,
        private CreateUploadFileUrlService $createUploadFileUrlService,
    )
    {
    }

    /**
     * @throws ConnectionException
     * @throws Exception
     */
    public function upload(
        UploadedFile $uploadedFile,
        string       $filePathWithNameOnOnedrive,
        ?string $parentFolderId = null,
    ): array|null
    {
        $token = $this->accessTokenService->getAccessToken();
        $uploadUrl = $this->createUploadFileUrlService->create($filePathWithNameOnOnedrive,$parentFolderId);
        $fileContent = $uploadedFile->getContent();
        $response = Http::withHeaders([
            'Authorization' => "Bearer $token",
        ])
            ->withBody($fileContent, 'text/plain')
            ->put($uploadUrl);
        if (!$response->successful()) {
            Log::critical('File path on onedrive: ' . $filePathWithNameOnOnedrive);
            Log::critical('Upload url: ' . $uploadUrl);
            Log::critical(json_encode($response->body()));
            throw new Exception('Unable to upload file');
        }
        return $response->json();
    }
}


<?php

namespace App\Services\Onedrive\FileUpload;

class CreateUploadFileUrlService {
    public function create(string $uploadFolderPath,?string $parentFolderId = null) : string{
        if(!empty($parentFolderId)){
            return "https://graph.microsoft.com/v1.0/me/drive/items/$parentFolderId:/$uploadFolderPath:/content";
        }
        return 'https://graph.microsoft.com/v1.0/drive/root:/'. $uploadFolderPath. ':/content';
    }
}

The storeFile method inside the onedrive controller uses the AccessTokenService to get an accessToken then finally uses the Upload file service to make the request to the onedrive api to store our file in onedrive and save the file metadata to our database. The /home route also displays already stored files and their download url in the page so we can view our files.

Spread the love

Leave a Reply

Your email address will not be published. Required fields are marked *