AuthMiddleware fails if Auth protected route is called via Ajax

I prepared an image uploader for the Summernote WYSIYWG Editor.

Basically the uploader is sending an ajax request to a certain url, this url should be protected by the AuthMiddleware from Rainlab User v3.

This here is the request from Summernote:

    function uploadImage(file) {
        let data = new FormData();
        data.append('file', file);

        $.ajax({
            url: '/upload/image',
            method: 'POST',
            data: data,
            contentType: false,
            processData: false,
            xhrFields: {
                withCredentials: true 
            },
            success: function(response) {
                if (response.url) {
                    $('#summernote-editor').summernote('insertImage', response.url);
                } else {
                    alert('Failed to upload image.');
                }
            },
            error: function() {
                alert('Error uploading image.');
            }
        });

Here’s my routes.php

Route::group(['middleware' => \RainLab\User\Classes\AuthMiddleware::class], function () {
    trace_log("this works!");
    Route::post('/upload/image', function (Request $request) {
        trace_log("This code is unreachable");
        return FileUploader::imageUpload($request);
    });
});

I also checked the AuthMiddleware from plugins/rainlab/user/classes. The request fails at !Auth::check()

class AuthMiddleware
{
    /**
     * handle the request
     */
    public function handle($request, Closure $next)
    {
        if ($jwtToken = $request->bearerToken()) {
            Auth::loginUsingBearerToken($jwtToken);
        }

        if (!Auth::check()) {
            return Response::make('Forbidden', 403);
        }

        return $next($request);
    }
}

I use session based authentication, but I get the feeling the user data in there is not properly send. That’s why I added this here to summernote:

            xhrFields: {
                withCredentials: true 
            },

but it didnt help. Right now I am lost, any help is welcome :slight_smile:

Thanks already ^^

Hey @LordRazen

Take a look at the docs on JWT auth:

1 Like

So, if I get it right, one of the solution ideas is to build the API endpoint with a page instead of within the routes directly.

So I did, this is my solution for now:

The Image Upload Page:

title = "API - Image Upload"
url = "/api/upload/image"

[session]
[apiUploadImage]
==
{% if this.request.method != 'POST' %}
    {% do response({ error: 'Only POST requests are allowed' }, 405) %}
{% endif %}

{% if not session.user %}
    {% do response({ error: 'Access Denied' }, 403) %}
{% endif %}

{% component 'apiUploadImage' %}

The apiUploadImage component:

use Cms\Classes\ComponentBase;
use October\Rain\Support\Facades\Auth;
use RainLab\User\Models\User;
use System\Models\File;
use Validator;

class APIUploadImage extends ComponentBase
{
    public function onRun()
    {
        $request = request()->all();
        if (!array_key_exists("file", $request)) return "no file";

        # Validate
        $validatedData = Validator::make(
            $request,
            ['file' => 'required|image|mimes:jpeg,png,jpg,gif,webp|max:2048']
        );
        if ($validatedData->fails()) {
            trace_log($this->onError($validatedData->errors()));
            return "validation fails";
        }

        # Uploaded File
        $uploadedFile = $request['file'];
        if (!$uploadedFile->isValid()) {
            return;
        }

        $user = Auth::getUser();
        if (!$user) {
            trace_log("no user, should never happen");
            return;
        }

        // Store 
        $path = $uploadedFile->store('uploads', 'local');

        // Create a new File model instance and populate its attributes
        $file = new File;
        $file->data = $uploadedFile;
        $file->fill([
            'attachment_id' => $user->id,
            'attachment_type' => User::class,
        ]);
        $file->save();

        return response()->json(['url' => $file->getPath()]);
    }
}

I know I didn’t handle the return statements correct right now.

What I would like to know if I got the idea right and if there’re some security problems which you can already see in my solution. Any feedback is welcome here. Gonna post an adjusted solution lateron…

Thx already :slight_smile:

1 Like