This looks long, but it’s simpler than it seems.
Scenario: You have an existing legacy PHP system, and you would like to gradually migrate to Laravel, you don’t want to migrate everything to laravel from the word go (because you either don’t have the time, or you need features from laravel but don’t want to learn laravel all at once, or maybe developing with laravel is your favorite way of learning laravel), whichever it may be, this tutorial is for you.
To get on track, I will assume that all you want for the time being is to have laravel (with socialite) take care of registering and authenticating users, and pass the logged in user data to your system… and leave other migrations for later, this is a good way to get started.
The steps
1- Install Laravel 12
Just a plain old vanilla setup. If you don’t know how to do this, I have the following post for you (Installing laravel 12 with social login), you don’t need to modify anything for this setup to work, yet you might find some stuff that needs some modification (Regardless of this setup), so here are a few modifications you may want to make
all you need now is to make sure you can register as a user, either directly or using social login (Or whatever you want), to speed things up, you might want to use this amazing starter kit here (https://github.com/Laravel-Uis/socialite-ui), bottom line is a clean Laravel install — nothing fancy..
2- One of the following two options depending on how your system is setup
Option 1– If your system already uses rewrite, have nginx rewrite all your requests to myrouter.php instead of index.php, here is the nginx config file I use, and here is the myrouter.php file
Elaboration: Normally, in a Laravel installation, nginx would rewrite all requests to laravel’s own index.php, what we have done with those two files is have it rewrite it to our own router (myrouter.php), if our existing system knows how to handle the request, it would, if it doesn’t , then it is probably a laravel page that the user is seeking (such as registration page), so we pass control to laravel.
Option 2– If you are not using rewrite in your legacy system, and PHP pages are accessed directly, the laravel recommended nginx config already deals with this since it checks for file existence before rewriting the request to index.php, all you will need to do at this stage is include this small file in your PHP files (require_once rather than include)
3- Integrating the auth system into ours
Our options: What we will do is create an endpoint on Laravel and have our system call it internally
What we won’t do: it is worth mentioning that there are quite a few ways besides the above, but none of them is straight forward, one of them is to include laravel’s internals (the console bootstrap path… what Artisan uses) into our own system, but Laravel’s sessions are serialized and encrypted ( with Illuminate\Encryption\Encrypter and the APP_KEY) so you will have to replicate Laravel’s decryption/unserialization logic,so we will stick to the auth endpoint… and optimize it by only firing it up when needed (To not load laravel for every walk in visitor to the website)
Note: If you will be going by my advice to develop them separately, what comes below needs laravel to function/test/develop, you will need to create a file that checks if laravel is available before firing up, and if not, send back a dummy testing user (or whatever you need), you can make that easier by toggling a “production” flag, and when in production it calls upon laravel, when in development, it serves the dummy user.
3.1 : Create an endpoint in laravel, very simple, Just edit laravel’s routes file ( routes/web.php) and add the following, it is also wrap it in middleware that only allows access from localhost, even though that is not really needed, but keeping security tight is a good idea (You can remove the extra wrapper by deleting everything from ->middleware through the end and putting a semi colon instead, up to you)
Note: I personally added this to /routes/auth.php alongside the login and registration features since it is authentication specific, but technically it makes absolutely no difference
use Illuminate\Support\Facades\Route;
Route::get('/auth/me', function () {
return response()->json([
'user' => auth()->check() ? auth()->user() : null,
]);
})->middleware(function ($request, $next) {
if ($request->server->get('SERVER_ADDR') !== $request->ip()) {
abort(403);
}
return $next($request);
});
Now, we need a function to talk to laravel internally and get the user ! we will fire up laravel, take the user data, and go back to our own system
And we also need to know when to call that function, there is no need to fire up Laravel then tear it all down when there is no chance that this might be a logged in user !
Previously, i had a function that fired up laravel internally, but after some digging, I realized that the overhead of calling it over http is very low, and that it will future proof the system (When laravel decide to change things)
function currentUser($http_port) {
static $cachedUser = null;
static $resolved = false;
if (!$resolved) {
$cachedUser = null; // default
if (!empty($_COOKIE['voodoo_session'])) {
//$ch = curl_init("https://www.voodoo.business/auth/me"); // Using your FQDN
$ch = curl_init("http://127.0.0.1:{$http_port}/auth/me");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 5); // optional timeout
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Host: www.voodoo.business',
'Cookie: voodoo_session=' . $_COOKIE['voodoo_session']
]);
$response = curl_exec($ch);
if ($response === false) {
error_log('currentUser cURL error: ' . curl_error($ch));
} else {
$data = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log('currentUser JSON decode error: ' . json_last_error_msg());
} else {
$cachedUser = $data['user'] ?? null;
}
}
curl_close($ch);
}
$resolved = true;
}
return $cachedUser;
}
Now you can simply call the function currentUser as follows, as many times as you wish, it will only invoke laravel once per web request
$user = currentUser();
if ($user) {
echo "Logged in as {$user['name']}";
} else {
echo "Not logged in";
}
Improvement – Optimization: You can short circuit earlier by simply connecting to the database and checking if the session id ($sessionId = $_COOKIE[‘laravel_session’];) exists in the database or the file or memory store, then, if it exists, invoke the currentUser function… this will ( check that the session file/row/key exists, not that it’s valid), but getting into this now will complicate the tutorial because there are 3 different scenarios and the solution we are at right now is good enough for most low to medium traffic websites
The logout button
Laravel does not have a page that logs you out as soon as you visit it, this will effectively disable CSRF ! and allow forced logout, so the answer is to create a logout page in Laravel and visit that page, it is very easy
The route file
/routes/web.php
Route::get('/log_me_out', function () {
return view('logout'); // blade template with the button
});
The Blade template
resources/views/logout.blade.php (If you are not familiar with laravel, the part of the file name that is before “.blade.php” is the view name, so the view in the route takes us here
<h1>Click below to log out</h1>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit">Logout</button>
</form>
All you need to do now is link to /log_me_out, and the user will be presented with the option to logout
Thoughts and ideas
I do not practice what I preach, my projects live inside of each other, the outer project is laravel, and my project lives inside a folder inside laravel (Not inside the public folder, just inside laravel’s project folder), at which point, myrouter.php either reads static files and serves them, or includes php files from my projects folder, so I only have 1 file inside the public folder….
So, in my projects folder, i do the regular steps below
cd /var/www/laravel/innderproject (This is the whole story, running composer inside the project folder)
composer init
composer require nikic/php-parser:^5.6 vlucas/phpdotenv:^5.6
And inside that folder, all files include the code
require __DIR__ . '/vendor/autoload.php';
This loads from the inner vendors folder, laravel knows how to reach the outer one