Laravel Socialite with socialite-ui

In a previous post, I explained how to install the third party (socialite-ui) on your website, which itself was written to help people with the “Gradually migrating to Laravel” post, but it seems it is not enough, so here is how to fully get socialite working, much of the work is outside your website though, and i won’t be covering that, all you need to do is ask google, for example, how do i enable social logins with google or facebook etc…

The redirect problem

After registering or logging in, your user will be redirected to an absolutely empty “/dashboard“, which is already handled by myrouter.php

Email verification

Before doing anything, let us check that email is working using your SMTP credentials

Create the following route for testing

use Illuminate\Support\Facades\Mail;

Route::get('/test-email', function () {
Mail::raw('This is a test email.', function ($message) {
$message->to('test@example.com')
->subject('Test Email');
});
return 'Email sent!';
});

socialite-ui does not come with email verification (during registration) by default, you will need to enable it, this is simply by following the instructions here

now edit the file (/config/socialite-ui.php) to enable the social logins you want enabled, and alter the other settings as per the page her (https://github.com/Laravel-Uis/socialite-ui)

php artisan config:clear
php artisan cache:clear

installing Laravel 12 with social login

Normally this would not have been worthy of a blog post of its own, there are plenty of posts that explain how to do this, but because I don’t want the post on “Gradually migrating to laravel” to be too long, I have decided to move the part on how to setup a vanilla install of laravel to its own post.

Rather than having you setup sociallite in laravel, there is an amazing starter kit on github (https://github.com/Laravel-Uis/socialite-ui) that we will be using to hit the ground running, this is not an official starter kit, it is by a third party, namely Joel Butcher, the same person behind the good old, now unmaintained socialstream (Socialite and JetStream)

So, let’s get down to business, I assume you have a debian machine up and running

apt install php-fpm nginx libnginx-mod-http-headers-more-filter openssl
apt install php-{dev,common,xml,tokenizer,zip,mysql,curl,mbstring,mysql,opcache,gd,intl,json,xsl,bcmath,imap,soap,readline,sqlite3,gmp,guzzlehttp-guzzle}

Now, make sure the following PHP extensions are enabled (i am pretty sure they are after the commands above) but to be safe, check in php.ini

extension=fileinfo
extension=mbstring
extension=openssl

Remember to “mariadb-secure-installation”, then create a username and database in mysql for laravel ! you can either use PHPMYADMIN or from the command line, makes no difference, remember to flush privileges

Install composer and nodejs

apt install composer nodejs npm
composer --version

Now, I am assuming your web directory is owned by the web server user ! first, install laravel globally, this is a new tool we will use to create laravel installations from now on

su - webdev (Whatever user you want to execute under)
composer global require laravel/installer

Now, add laravel to your path in .bashrc

export PATH="$HOME/.config/composer/vendor/bin:$PATH"
source ~/.bashrc
echo $PATH

IMPORTANT: In my case, all my websites are in folders that follow the patern “/var/www/vhosts/com.websitename.www/public”… hence, i execute the lines below from within the “/var/www/vhosts/” directory ! once i execute the below line, i will have a folder called my-app, I usually rename that to com.websitename.www and there is already a public folder in there, The long and short of this story is, your whole project folder with the public directory inside is creating from the command below, if you do it wrong, you can simply move the files and folders in that folder to where they should be

And now, in your project directory (Which is one level below your public web directory), execute the following, the laravel command is the new way, and if you are not looking for Livewire Volt, just visit the github page to see your 3 other options (Vue, React, and Livewire without volt)

laravel new my-app --using=laravel-uis/socialite-ui-livewire-starter-kit

During the install, it will ask you what testing framework you want, You can pick whatever one you prefer, then it will ask you if you would like to run “npm install and npm run build?”, You can hit no, and run the following command yourself

npm install && npm run build

Configure

Now, edit your .env ! the default one uses sqlite, if you want to use mysql / mariadb (like most PHP people), just make sure this becomes your database in your .env file

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

Now, run the following commands

php artisan migrate
The following commands are for reference only
php artisan session:table
php artisan queue:table
php artisan cache:table
php artisan migrate
php artisan config:clear
php artisan route:clear
php artisan view:clear
php artisan cache:clear
php artisan migrate:status

Behind Proxy

If you are behind a proxy, you might need to tell laravel 12 to trust proxies… (Edit /bootstrap/app.php), and add the parts

first, add this line at the top

use Illuminate\Http\Request;
$middleware->trustProxies(
//at: '*', // Trust all proxies (use for dynamic/cloud setups like Nginx on the same server; see security notes below)
at: ['127.0.0.1', '192.168.1.0/24', 'xxx.xxx.xxx.xxx'],
headers: Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB // Include if using AWS; optional for standard Nginx
);

Right where there is an empt comment inside

->withMiddleware(function (Middleware $middleware) {

Gradually Migrating a legacy system to Laravel !

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

Trixie… php8.4-imap missing

Error: Unable to locate package php8.4-imap
Error: Couldn’t find any package by glob ‘php8.4-imap’

the debian package maintainer (Ondřej Surý) has released it on his repository, but it is nowhere to be found for Debian 13 on “https://packages.debian.org/“, trixie didn’t trickle down to the website yet, but in SID, it reads “The extension is unbundled from php-src as of PHP 8.4.0.”

I am not yet sure what this means, there is a chance it means we should install it using PECL, I will not do that, I would rather installwith apt, but if i wanted to, i would execute

sudo apt install php-pear php8.4-dev
sudo pecl install imap

There is a chance that it will be coming back to debian’s repositories since Sury (The package maintainer) has it, but I have no idea when

So, let us install it (the IMAP extension and nothing else) from the sury repository to make things simpler when debian has it again

Let us start by adding

sudo apt install -y lsb-release apt-transport-https ca-certificates curl gnupg2
echo "deb https://packages.sury.org/php $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/sury-php.list
curl -fsSL https://packages.sury.org/php/apt.gpg | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/sury-php.gpg
sudo apt update

Now we have added Sury’s repository to apt, let us go ahead and pin it so that only php8.4-imap is sourced from that repo !

sudo tee /etc/apt/preferences.d/sury-php.pref >/dev/null <<'EOF'
Package: *
Pin: release o=deb.sury.org
Pin-Priority: 1

Package: php8.4-imap
Pin: release o=deb.sury.org
Pin-Priority: 990
EOF
sudo apt update

Now install

sudo apt install php8.4-imap

To make sure everything is correct

apt policy php8.4-imap

Now in my case, I need to restart PHP (systemctl restart php8.4), in your case, you might need to restart apache or something else, when in doubt, restart the machine 😛

Once the package is in debian repositories again, we will simply remove /etc/apt/preferences.d/sury-php.pref and /etc/apt/sources.list.d/sury-php.list, then reinstall php8.4-imap.

BeautifulSoup

BeautifulSoup is a python package that allows you to extract data from HTML files, it is very easy and intuitive

Let us assume you have an HTML page !

First, let us assume you want the title from that HTML page….

mysoup = BeautifulSoup(response.content, 'html.parser')
title = soup.title.string if soup.title else "No title found"

Now, assuming you want to remove everything that has to do with CSS and presentation, you can remove the following things with this easy code snippet, then putting whatever is lef in a variable called text

for irrelevant in mysoup.body(["script", "style", "img", "input"]):
irrelevant.decompose()
text = soup.body.get_text(separator="\n", strip=True)

Python virtual environment with pip

If you are familiar with python, you probably are also familiar with pip, and very likely familiar with venv (Virtual environments)

There is nothing special in particular about this for AI, it is exactly as you would for any other installation.

For AI, i would totally recommend anaconda, but if for some reason that is not an option, this option will do 100% of the time

So, you need to start by installing Python 3 !, On debian, I would just use the repository with apt, it may be true at the time of writing that in the repo it uses 3.11 rather than the latest 3.13, but that is absolutely fine

sudo apt update
// The following command should do
sudo apt install python3
//But i would much rather install everything python in one go
apt install build-essential wget git python3-pip python3-dev python3-venv \
python3-wheel libfreetype6-dev libxml2-dev libzip-dev libsasl2-dev \
python3-setuptools

Now, with that out of the way, navigate to the project’s folder (Assuming you have downloaded a project for example), and create a virtual environment

python3 -m venv venv

Now, you can activate that project with

source venv/bin/activate
//On windows, the above should look something like
venv\Scripts\activate

That is basically it, you will now need to, from within the command prompt of the venv, install dependencies either one by one using the pip command, or point it to a file containing the dependencies, for example

pip install -r requirements.txt

You should now be good to go !

Setting up Anaconda for AI

What is Anaconda ?

Conda, Like pip, is a python package manager, but conda is probably undisputed as the more thorough solution of the two, with better support for non-python packages (pip has very limited support) and support for more complex dependency trees

To clarify things, conda is the package manager while Anaconda is a bigger bundle, if you want to install conda alone, you are probably looking to install Miniconda. Anaconda is a set of about a hundred packages including conda, numpy, scipy, ipython notebook, and so on.

So, let us go through installing and using Anaconda on all 3 platforms, Windows, Linux and Mac

Linux

On Debian, there is no Anaconda package, to install, you will need to download the download script from anaconda and install it (Or conda, or miniconda for that matter) , you can add miniconda to apt using the “https://repo.anaconda.com” repo if you are willing to add it (apt install conda), but here I will assume you will just install Anaconda, and the only orthodox way to do that is with the installation script

Download the Anaconda installer from the Anaconda Website (There is a button that reads skip registration if you don’t want to give them your email address)

https://www.anaconda.com/download

Navigate to the downloads folder, and execute the script just downloaded, in my case, the script’s name was Anaconda3-2024.10-1-Linux-x86_64.sh so I execute the following

cd /home/qworqs/Downloads
chmod 0777 Anaconda3-2024.10-1-Linux-x86_64.sh
./Anaconda3-2024.10-1-Linux-x86_64.sh

After accepting the agreement, I see a message asking me to accept the license agreement, hit enter, take a look at the license agreement, hit the letter (q) to exit the agreement, then you will be asked if you accept the agreement, assuming you agreed to it, you will next be presented with….

Anaconda3 will now be installed into this location:
/home/qworqs/anaconda3

To which i accepted the suggested location

Now, I opt to keep the installer in the downloads directory just in case something goes wrong, but you can safely delete the 1GB installer if you like !

At the end of the installation, the installer offers to update your shell, in my case, i opted NOT TO, if you opted otherwise, you can always “set auto_activate_base” to false….

Do you wish to update your shell profile to automatically initialize conda?
This will activate conda on startup and change the command prompt when activated.
If you'd prefer that conda's base environment not be activated on startup,
run the following command when conda is activated:

conda config --set auto_activate_base false

You can undo this by running `conda init --reverse $SHELL`? [yes|no]

Once i answered no, i was presented with the following message

You have chosen to not have conda modify your shell scripts at all.
To activate conda's base environment in your current shell session:

eval "$(/home/voodoo/anaconda3/bin/conda shell.YOUR_SHELL_NAME hook)"

To install conda's shell functions for easier access, first activate, then:

conda init

Thank you for installing Anaconda3!

The environment

For convinience, let us start by adding our Anaconda3 installation to system path

//First, add anaconda to path by editing either ./bashrc OR ~/.bash_profile (Depending on which one you have), and adding the following to the bottom of the file

export PATH=~/anaconda3/bin:$PATH

Now, to apply the changes, you should either close the terminal window and re-open it, or run the command “source ~/.bashrc” or “source ~/.bash_profile”

To check whether the magic happened, run the command “conda –version“, in my case, that returned “conda 24.9.2”

Now, from this stage on, conda is installed, but to be able to use it, you should have a project ! so now you can move on (The index page), and I will explain how to run your project where needed, here, for completion, I will assume you have a project and put the instructions here (You know you are in the project dir when you see a yaml file commonly called environment.yml)

So, again, to activate an environment, there is a yaml file for every project that contains the dependencies of that project ! let us assume you are in your project’s directory, and the yaml file is called environment.yml , the following command will create a python sub-environment, and install all the dependencies in that yaml file, be sure you are in the directory with the yaml file

Now, to create a virtual environment, cd into the directory that has your project and run the following

conda env create -f environment.yml

Once the above is done downloading and installing, you should get a message like the one below

#
# To activate this environment, use
#
# $ conda activate projectName
#
# To deactivate an active environment, use
#
# $ conda deactivate

Now, next time, when you open a terminal, and want to activate an environment

1- conda init (conda deactivate to reverse), you only need to do this once
2- open a new shell
3- conda activate ProjectName (Also, conda deactivate to exit)

My Flutter notes

I recommend you read this after reading My Dart Notes.

this document is a reminder of everything Dart related ! There is a different one for flutter.

Why ? Because I have a problem, I don’t limit myself to a few languages like normal human beings, I know A LOT of programming languages, and when you don’t use a language long enough, you start messing up which syntax works where, so I create those small “Reminder” pages that I can skim through in an hour before I start working with any language after leaving it for some time !

Mind you, I eventually lose the page “in the (It is on some hard drive somewhere) and that is because after using it a couple of times, I no longer need it. but when i come across old ones, I will post them on this blog.

I am posting this one online because I am composing it now, and I haven’t lost it yet

Continue reading “My Flutter notes”

My Dart Notes

A quick revision of the Dart language syntax

this document is a reminder of everything Dart related ! There is a different one for flutter.

Why ? Because I have a problem, I don’t limit myself to a few languages like normal human beings, I know A LOT of programming languages, and when you don’t use a language long enough, you start messing up which syntax works where, so I create those small “Reminder” pages that I can skim through in an hour before I start working with any language after leaving it for some time !

Mind you, I eventually lose the page “in the (It is on some hard drive somewhere) and that is because after using it a couple of times, I no longer need it. but when i come across old ones, I will post them on this blog.

I am posting this one online because I am composing it now, and I haven’t lost it yet 😉

Classification

  • AOT Compiled for Production, or JIT compiled during development
  • Can also transpile into Javascript
  • Uses Garbage Collector (When AOT, it is bundled with compiled app)
  • Dart is statically typed (Fixed variable types)
  • sound null safe (sound = No mixing of safe and unsafe)
Continue reading “My Dart Notes”

Dart-Flutter VS Kotlin Multi Platform

I am writing this blog post to keep my thoughts in order ! because, true to my nature, I will keep questioning this choice and I will end up going back and forth between writing code in Kotlin and Dart-Flutter, which is not good, because I end up with software that is not uniform, as usual

After reading a book on flutter, and a book on Kotlin multi platform, It looks like it is going in the direction of flutter, It is true that KMP – iOS is now in beta, and things should now be looking good for KMM and KMP, it is not a no brainier and here is why I decided to go with Flutter

First of all, I understand that flutter may or may not lose google’s support any minute now, this is true because google loves to abandon projects, so they just might, everyone online is saying that there are no signs of google dropping support, but the truth is, I can see the effort being put into Jetpack compose and compare it to Flutter, in any case only upper management at google may (or may not) know yet, In any case, Flutter is open source, and even if they drop support today, it will probably still work many years from now, and when it doesn’t, I have never had problems learning new languages in a couple of weeks.

I have been a C++ developer for 25 years, and earlier this year (It is still 2024 right ?), I started learning RUST, the start was hell, my inner child did not want to leave the comfort zone, and kept making up reasons why C++ is eternal and better than RUST, but after some time, I was convinced that we crossed the point of no return, and I started, slowly but steadily to fall in love with RUST.

Now RUST excels in front end and graphical user interfaces (Said no one ever)…. But if I am planning to run rust on Android or iOS, boy oh boy, I might have to reinvent computers

So, the answer is that the front end should be handled by either Dart-Flutter or Kotlin-Multi platform

Now, Why dart-flutter rather than Kotlin Multi Platform and Compose Multi Platform

Before I dive into this, the parts of the application that are expected to require performance are probably going to be written in RUST, while permissions and GUI are expected to be handled by flutter, So why flutter

1- Leaf calls, that is, Leaf calls will not permit the garbage collector to run, which means that it is safe to pass a pointer to underlying data to C, provided that C does not hold on to that pointer after the FFI call returns. This removes the need to copy the data before passing it to C. But we are not planning to use C, we are interested in RUST ! the answer is that this is a proof of concept, also “opaque types” are possible 😉

Ownership and lifetimes in FFI are probably more of a problem in Kotlin as it stands today ! this is because flutter_rust_bridge provides a level of abstraction to avoid dangling pointers etc….

In Kotlin, exposing Rust functions as C-compatible functions will have a library one day (We already have UniFFI ( UniFFI-RS by mozilla ) but it is not as mature, there are variants of this library to do the job, and for kotlin multiplatform it is here (uniffi-kotlin-multiplatform-bindings) also worth mentioning that there is a UniFFI variant for Dart-Flutter !

Rust code will interact with the Android Java APIs through the Java Native Interface (JNI), and only partly controlled by flutter, there is also no reason to assume that a lot of data is going to travel between dart and RUST in most applications, but when there is such a scenario, we know what to do, we chose flutter.