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.