ahoi's avatar
Level 5

Validate CSRF token on API controller for SPA

Hi everyone,

I wrote a little Vue SPA app that uses the Laravel API. Now I'd like to protect the endpoint for sending a contact form so that the data can not be posted from using Postman or other tools.

So in my JS entry point, I saw this:

/**
 * We'll load the axios HTTP library which allows us to easily issue requests
 * to our Laravel back-end. This library automatically handles sending the
 * CSRF token as a header based on the value of the "XSRF" token cookie.
 */

window.axios = require ('axios');

window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

The application.blade.php that loads the Vue-app includes

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

This is my api route:

/*Contact Routes*/
Route::group(['prefix' => 'contact'], function ($router) {

    /*Send*/
    Route::post('/send', 'ContactApiController@send')
        ->name('api.contact.send');
});

This is the middleware group config in Kernel.php

  /**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups
        = [
            'web' => [
                \App\Http\Middleware\EncryptCookies::class,
                \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
                \Illuminate\Session\Middleware\StartSession::class,
                // \Illuminate\Session\Middleware\AuthenticateSession::class,
                \Illuminate\View\Middleware\ShareErrorsFromSession::class,
                \App\Http\Middleware\VerifyCsrfToken::class,
                \Illuminate\Routing\Middleware\SubstituteBindings::class,
                \Spatie\ResponseCache\Middlewares\CacheResponse::class,
            ],

            'api' => [
                'throttle:60,1',
                'bindings',
            ],
        ];

How can I add CSRF-protection to the api route in this case? Would it be enough to add \App\Http\Middleware\VerifyCsrfToken::class to the api group?

0 likes
7 replies
ahoi's avatar
Level 5

Hi @pom and thanks a lot for your message.

As I understood airlock is a lightweight user auth system that can be used as alternative to passport.

I don't want to register users or check, whether they are logged in or not - it's just about preventing automated post-requests (contact formular spam) and check, if the request is coming from the HTML form or not.

Maybe I misunderstood you - so I am very happy to get an update here :-)

thewebartisan7's avatar
Level 14

The answer is not simple, since there is many consideration to take into account.

If your API is accessed only by your SPA application then it's better to use JWT (https://jwt.io/ ) or Airlock (as written in documentation quoting "Airlock does not use tokens of any kind. Instead, Airlock uses Laravel's built-in cookie based session authentication services. This provides the benefits of CSRF protection, session authentication, as well as protects against leakage of the authentication credentials via XSS. Airlock will only attempt to authenticate using cookies when the incoming request originates from your own SPA frontend."

However if your API is accessed by third party, then they are "stateless", "as per the REST (REpresentational “State” Transfer) architecture, the server does not store any state about the client session on the server side... Session state is therefore kept entirely on the client. client is responsible for storing and handling all application state related information on client side."

Read more here https://stackoverflow.com/questions/2392100/how-to-prevent-csrf-in-a-restful-application

Shortly, if you are building an VUE SPA and need to use sessions, csrf, etc, then you can use web routes, which already include all sessions, csrf, etc. Otherwise you must include all middleware like in web, not sure exactly whichbut for sure sessions, cookies and csrf middleware.

I also use Passport a year ago for a project, but it was not best for me, and I discover it too late. Because Passport allow even machine to machine communication, while in my case, and I think also in your case, is not required. I have write here about it https://laracasts.com/discuss/channels/laravel/passport-password-grant-tokens and someone recently report this issues is reported here https://github.com/laravel/passport/issues/984

I hope this help.

1 like
ahoi's avatar
Level 5

Thank you very much for your help. I really appreciate this.

One thing is not clear for now: If I want to "protect" my contact form's api endpoint with airlock, I need to provide a login-function, right?

I would like to have this contact form public but without direct access of the api-endpoint. Is that possible? If yes I'd love to see an example or documentation.

Thanks in advance :-)

pom's avatar

If you aren't authorising users you could just put the POST route for your contact form inside the web routes file, it will then be protected by the CSRF middleware.

1 like
thewebartisan7's avatar

@ahoi I never check into Airlock yet since is new, but checking docs here https://laravel.com/docs/master/airlock#protecting-spa-routes, you can protect routes using middleware 'auth:airlock'. So your submissions routes can have this middleware, while your routes that display form can be without. But not sure why you should do that, if only authenticated users can submit form, then also display form should be protected.

1 like
ahoi's avatar
Level 5

Hi @pom

That's a possibility, I will think about that :-)

@zoroaster

I think I was not clear enough with my explanation: The contact form is public. I just want to prevent the api-endpoint from being accessed by external POST requests, so people could easily spam this form by fireing POST-requests to the API-endpoint.

1 like

Please or to participate in this conversation.