use gravatar images for user profiles in laravel jetstream

Srinath Reddy • April 2, 2021

laravel

Laravel jetstream is a feature rich application starter kit with most common features required by an application already built into it. One of the many amazing features of laravel jetstream is user profiles.

Laravel jetstream comes with a feature which allows your application users to upload their profile images. Once a profile picture is uploaded, It will be displayed in the navigation menu and used as a dropdown trigger for profile dropdown. This feature can be enabled in the config/jetstream.php configuration file.

I am a huge fan of displaying user profile images in my application. But I personally don't really like the idea of having a file upload feature in my applications. I try to avoid the file upload feature in my applications as much as possible. This is mainly due to the security issues being raised by having a file upload feature.

Although laravel jetstream is pretty secure and allows only image files to be uploaded from the profile section, it prevents me from turning off file uploads in php. If my application does not have a file upload feature, i will disable the file uploads in php.ini configuration file. This is to prevent attackers exploiting any known vulnerabilities in any third party package, or other parts of code which grants them access to upload an arbitrary file to my server.

I encourage you to do the same and disable file uploads in your php.ini configuration file if your application does not require file uploads.

Although security is the main reason for me not to use profile image upload feature, There are other issues with having user uploaded files such as

To avoid all the above mentioned issues, I prefer to add file upload feature to my application only if it is an essential part of the application.

Anyways, I took most of your time away from what you are actually here for. So I won't waste any of your time anymore and let's dive right into how we can customize laravel jetstream to use gravatar images instead of user uploaded profile photos.

what is gravatar?

For those of you who are not familiar with gravatar, You can get profile image for any user just by using their email address. If the user has an account on gravatar.com and has a profile picture added to it, We can easily get the profile picture and use it in our applications. Gravatar images are used by many applications worldwide to display the user profile picture without actually having a profile picture upload feature in their applications.

Customizing laravel jetstream to use gravatar profile photos

Gravatar images can be used in laravel jetstream simply by overriding a method and commenting out few lines of code. Let's see how we can do that.

1. Enable profile photos feature in jetstream

The very first step is to enable profile photos feature in laravel jetstream if you haven't already enabled it. This can be done in the config/jetstream.php configuration file. Uncomment the below line from the features array.

    'features' => [
         Features::profilePhotos(),
    ],

Once the above line is uncommented, You will start seeing a default placeholder image in navigation. You will also see the profile picture upload option in the user profile settings page. We will be disabling this feature in few minutes.

2. Override default behaviour to retrieve profile images

Once we enable the profile photos feature, We now need to override the way laravel jetstream retrieves the profile images. By default, Laravel jetstream retrieves the profile images by retrieving them from the storage location. We need to override this logic and let jetstream use the gravatar image urls instead of the profile image urls from storage.

We can do this by overriding the getProfilePhotoUrlAttribute method in your User model. This is a method in HasProfilePhoto php trait which is added to your user model. Add the below two method to your user model.

    // app/Models/User.php

    /**
     * @return string
     */
    public function getProfilePhotoUrlAttribute()
    {
        // You can add any of the gravatar supported options to this array.
        // See https://gravatar.com/site/implement/images/
        $config = [
            'default' => $this->defaultProfilePhotoUrl(),
            'size' => '200' // use 200px by 200px image
        ];

        return 'https://www.gravatar.com/avatar/'.md5($this->email).'?'.http_build_query($config);
    }

    /**
     * @return string
     */
    public function defaultProfilePhotoUrl()
    {
        return 'https://ui-avatars.com/api/'. implode('/', [

            //IMPORTANT: Do not change this order
            urlencode($this->name), // name
            200, // image size
            'EBF4FF', // background color
            '7F9CF5', // font color
        ]);
    }

As soon as you add the above methods to your user model, You will start seeing the gravatar images near the profile picture section in the navigation menus and profile settings page. Ofcourse you can customize the default image to use by changing the defaultProfilePhotoUrl method.

3. Remove upload profile picture button from settings page

Now that we have successfully switched our jetstream application to display gravatar profile images, We need to prevent users from uploading any of their images directly to our application. To do this, We need to comment our or remove few lines of code.

Laravel Livewire Stack

If you are using laravel livewire stack for your jetstream application, First publish livewire assets by running

php artisan vendor:publish --tag=jetstream-views

Now edit resources/views/profile/update-profile-information-form.blade.php file and comment out or remove the following lines.

If you haven't made any changes to this file previously, Most probably these are the lines from line 14 to line 25.

    <!-- Profile Photo File Input -->
    <input type="file" class="hidden"
        wire:model="photo"
        x-ref="photo"
        x-on:change="
            photoName = $refs.photo.files[0].name;
            const reader = new FileReader();
            reader.onload = (e) => {
                photoPreview = e.target.result;
            };
            reader.readAsDataURL($refs.photo.files[0]);
        " />

Now comment out the following code as well. This will most probably be from line 34 to line 51.

   <!-- New Profile Photo Preview -->
    <div class="mt-2" x-show="photoPreview">
        <span class="block rounded-full w-20 h-20"
              x-bind:style="'background-size: cover; background-repeat: no-repeat; background-position: center center; background-image: url(\'' + photoPreview + '\');'">
        </span>
    </div>

    <x-jet-secondary-button class="mt-2 mr-2" type="button" x-on:click.prevent="$refs.photo.click()">
        {{ __('Select A New Photo') }}
    </x-jet-secondary-button>

    @if ($this->user->profile_photo_path)
        <x-jet-secondary-button type="button" class="mt-2" wire:click="deleteProfilePhoto">
            {{ __('Remove Photo') }}
        </x-jet-secondary-button>
    @endif

    <x-jet-input-error for="photo" class="mt-2" />

Inertia Stack

If you are using inertia stack for laravel jetstream, Edit resources/js/Pages/Profile/UpdateProfileInformationForm.vue file and comment out or remove the following lines.

If you haven't made any changes to this file previously, Most probably these are the lines from line 14 to line 17.

    <!-- Profile Photo File Input -->
    <input type="file" class="hidden"
        ref="photo"
        @change="updatePhotoPreview">

Now comment out the following code as well. This will most probably be from line 26 to line 41.

    <!-- New Profile Photo Preview -->
    <div class="mt-2" v-show="photoPreview">
        <span class="block rounded-full w-20 h-20"
              :style="'background-size: cover; background-repeat: no-repeat; background-position: center center; background-image: url(\'' + photoPreview + '\');'">
        </span>
    </div>

    <jet-secondary-button class="mt-2 mr-2" type="button" @click.prevent="selectNewPhoto">
    Select A New Photo
    </jet-secondary-button>

    <jet-secondary-button type="button" class="mt-2" @click.prevent="deletePhoto" v-if="user.profile_photo_path">
    Remove Photo
    </jet-secondary-button>

    <jet-input-error :message="form.errors.photo" class="mt-2" />

This will remove the upload profile picture button from the profile settings page.

4. Link to gravatar.com to change profile picture

Now that we changed the entire profile picture functionality to use gravatar.com, we need to link back to gravatar.com so, our users knows where to update their profile picture. Let's add the following piece of code to our profile settings page to link to gravatar.com.

    <p class="mt-2">Update your profile photo on <a href="https://gravatar.com" target="_blank" class="text-indigo-700">gravatar.com</a></p>

Livewire Stack

Add the above code in resources/views/profile/update-profile-information-form.blade.php file below the profile picture. Around line 33 as shown below.

    <!-- Current Profile Photo -->
    <div class="mt-2" x-show="! photoPreview">
        <img src="{{ $this->user->profile_photo_url }}" alt="{{ $this->user->name }}" class="rounded-full h-20 w-20 object-cover">
    </div>
    <p class="mt-2">Update your profile photo on <a href="https://gravatar.com" target="_blank" class="text-indigo-700">gravatar.com</a></p>

Inertia Stack

Add the above code in resources/js/Pages/Profile/UpdateProfileInformationForm.vue file below the profile picture. Around line 25 as shown below.

    <!-- Current Profile Photo -->
    <div class="mt-2" v-show="! photoPreview">
        <img :src="user.profile_photo_url" :alt="user.name" class="rounded-full h-20 w-20 object-cover">
    </div>
    <p class="mt-2">Update your profile photo on <a href="https://gravatar.com" target="_blank" class="text-indigo-700">gravatar.com</a></p>

5. Disable profile picture uploads in backend code

By following all the above steps, We are now successfully using gravatar images for user profiles and also disabled uploading of profile images from the UI. There is one step remaining which is disabling of profile images from backend code. We can do this by overriding the updateProfilePhoto and deleteProfilePhoto methods of HasProfilePhoto trait which is added to our user model.

Add the below two methods to your user model.

    // app/Models/User.php

    /**
     * @param  \Illuminate\Http\UploadedFile  $photo
     * @return void
     */
    public function updateProfilePhoto(\Illuminate\Http\UploadedFile $photo)
    {
        return;
    }

    /**
     * @return void
     */
    public function deleteProfilePhoto()
    {
        return;
    }

6. Optional - Disable file uploads completely

As I mentioned initially, If your application does not have any feature which allows a user to upload a file, It is better to disable file uploads completely to avoid attackers exploiting any known vulnerabilities in third party packages which allows file uploads. You can disable file uploads by settings file_uploads=Off in your php.ini configuration file.

Did you enjoy reading the above article? share your thoughts with me on twitter. Also be sure to check out my recent posts.