Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

secondman's avatar

CSRF and Middleware

Ok this is running me in circles.

I'm building an SDK with Guzzle so that developers can build modules for different ecomm platforms to interact with our Lumen API.

Our api key structure is similar to Stripe where we have public and secret keys for both testing and live.

Developers/customers can create applications which just basically consists of urls and api keys. Applications are stored in the applications table, keys stored in the keys table.

Applications have a one to one relationship to a row of keys, keys belong to an application.

What we want to flow to look like is this:

  1. SDK makes a post to the API with app id and either test or live public key.
  2. The Auth controller will verify and sort whether the application is live or test and return a bcrypted string of the application's test or live secret key, and the csrf token as a response.
  3. The SDK makes a post with the returned bcrypted key and the plain test version of the correct secret key, and the csrf token. These are compared on the server and if they match, an access token is returned to the SDK, which is then stored in either local storage or a session and are used to execute any operations the user wishes to make against the API.

The problem is that in order to get csrf tokens, I need to enable the middleware. With the middleware enabled, I can't make a post to Lumen without a token, or a get a token mismatch error. An obviously I can't get a csrf token without making a call to the API.

It's a big circle jerk.

I'm using Guzzle 6 fyi.

Any ideas on how to get this working?

0 likes
19 replies
jimmck's avatar
jimmck
Best Answer
Level 13

Why even bother with CSRF token. Just use JWT token with routes that don't need CSRF token. If you really want to use CSRF tokens. Then login get your JWT token and when a CSRF token expires use a JWT protected call to get a new one.

    Route::get('token', ['middleware' => 'jwt.auth', function () {
        //response()->myMessage(csrf_token());
        $token = JWTAuth::getToken();

        return (new Response(csrf_token()))->header('Authorization', 'Bearer ' . $token->get());

        //return new Response(csrf_token());
    }]);

This gets a fresh CSRF token and refreshed JWT token (when expired).

Here is a modified Tymons middleware example of JWT auth.

<?php

namespace Tymon\JWTAuth\Middleware;

use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;

class GetUserFromToken extends BaseMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, \Closure $next)
    {
        $expired = false;

        if (! $token = $this->auth->setRequest($request)->getToken()) {
            return $this->respond('tymon.jwt.absent', 'token_not_provided', 400);
        }

        try {
            $user = $this->auth->authenticate($token);
        } catch (TokenExpiredException $e) {
            //return $this->respond('tymon.jwt.expired', 'token_expired', $e->getStatusCode(), [$e]);
            $expired = true;
        } catch (JWTException $e) {
            return $this->respond('tymon.jwt.invalid', 'token_invalid', $e->getStatusCode(), [$e]);
        }

        if ($expired) {
            try {
                $newToken = $this->auth->setRequest($request)
                  ->parseToken()
                  ->refresh();
                $user = $this->auth->authenticate($newToken);
            } catch (TokenExpiredException $e) {
                return $this->respond('tymon.jwt.expired', 'token_expired', $e->getStatusCode(), [$e]);
            } catch (JWTException $e) {
                return $this->respond('tymon.jwt.invalid', 'token_invalid', $e->getStatusCode(), [$e]);
            }
            // send the refreshed token back to the client
            $request->headers->set('Authorization', 'Bearer ' . $newToken);
        }

        if (! $user) {
            return $this->respond('tymon.jwt.user_not_found', 'user_not_found', 404);
        }

        $this->events->fire('tymon.jwt.valid', $user);

        return $next($request);
    }
}
secondman's avatar

Thanks @jimmck but I don't see how this addresses anything in my question.

I need to get the system I have running, not rewrite it all to use JWT. Perhaps I wasn't clear in my question.

  1. How do I bypass csrf for my initial authorization post?
  2. Should a move the Laravel\Lumen\Http\Middleware\VerifyCsrfToken::class from middleware to routeMiddleware?
  3. If csrf middleware isn't enabled on my auth route, how do I create a new token to send back to SDK?
  4. Which of the default middleware needs to be enabled when using Lumen as an API?
$app->middleware([
    Illuminate\Cookie\Middleware\EncryptCookies::class,
    Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
    Illuminate\Session\Middleware\StartSession::class,
    Illuminate\View\Middleware\ShareErrorsFromSession::class,
    Laravel\Lumen\Http\Middleware\VerifyCsrfToken::class,
]);

These are all disabled by default and everything still works fine, but I'm sure some of them would be valuable or should I simply leave them all turned off?

Hopefully that clarifies the question a bit.

jimmck's avatar

So your API is open to all to use? If you have Middleware disabled, you have no CSRF issue. My example show you how to get a new token with a protected route. If you are not using authentication you have no issues. In your above post the middleware is turned on, it is not commented out. If you want to detect CSRF errors you should modify the error handler to send a more explicit error message so your API can catch it. csrf_token() gets the current token. Tokens are session bound.

secondman's avatar

@jimmck

So your API is open to all to use?

No, it requires users to have a valid application, and they must authorize with their application's id and secret keys.

If you are not using authentication you have no issues.

Are you referring to the built in user authentication?

In your above post the middleware is turned on, it is not commented out.

Correct, this is part of what I'm asking, which middleware should I have turned on for a simple API?

secondman's avatar

Ok so we're back to the original question.

How do I submit the first authorization post request when I don't have a csrf token?

I have 2 concrete implementations Connection and Authenticate bound with service providers to Pimple container.

The Connection class creates and handles all Guzzle functions, create the client, creates and format requests and so on.

The Authenticate class handles the setting of application ids and client id/secret keys.

My Authenticate::request() method looks like so:

public function request() {
    
    $client = app('client');

    $request = $client->request('POST', 'auth', [
        'form_params' => [
            'app_id' => $this->app_id,
            'public' => $this->client_id,
        ]
    ]);
    
    return $request;
}

This creates a Psr7 request object from the Connection::request() method:

public function request($method, $uri, $data = []) {

    return new Request($method, $uri, $data);
}

So now I build up my authorization request to send to the API:

$client   = app('client')->provider();
$request  = app('auth')->request();

$response = $client->send($request);

When this executes I get the following error:

[2015-09-27 18:31:58] lumen.ERROR: exception 'Illuminate\Session\TokenMismatchException' in /home/vagrant/code/myapp/api/vendor/laravel/lumen-framework/src/Http/Middleware/VerifyCsrfToken.php:46

Clearly because I have no csrf token posted in my request. If I turn off the Laravel\Lumen\Http\Middleware\VerifyCsrfToken middleware in Lumen the post request goes through just fine.

How do I make a post on my initial API request when I don't yet have a csrf token to match up?

secondman's avatar

@jimmck

I've made quite a bit of progress. I'm now able to to create an authorization and create an access token for my app.

Authenticate class:

class Authenticate {

    protected $app_id;
    protected $client_id;
    protected $secret_id;

    protected $client;
    protected $provider;

    protected $token = false; // csrf token
    protected $access_token = false; // access token

    public function __construct() {
        
        $this->app_id    = env('APP_ID', '');
        $this->client_id = env('CLIENT_ID', '');
        $this->secret_id = env('CLIENT_SECRET', '');

        $this->client    = app('client');
        $this->provider  = $this->client->provider();
    }

    public function authorize() {

        if (! isset($_SESSION['sb_access_token']) || ! isset($_SESSION['sb_token'])):

            $this->csrf();
            $auth_token = $this->request();
            $this->requestToken($auth_token);

        endif;
    }

    public function setAppId($app_id) {
        
        $this->app_id = $app_id;

        return $this;
    }

    public function setClientId($client_id) {
        
        $this->client_id = $client_id;

        return $this;
    }

    public function setSecretId($secret_id) {
        
        $this->secret_id = $secret_id;

        return $this;
    }

    public function checkCsrf() {

        return $this->token;
    }

    public function accessToken() {

        return $this->access_token;
    }

    public function requestToken($token) {

        $response = $this->provider->post('auth/token', [
            'form_params' => [
                'app_id' => $this->app_id,
                'secret' => md5($this->secret_id),
                'token'  => $token,
                '_token' => $this->token,
            ]
        ])->getBody(true);

        $this->access_token = (string)$response;

        $_SESSION['sb_access_token'] = $this->access_token;

        return $this->access_token;
    }

    public function csrf() {
        
        $token = $this->provider->get('auth')->getBody(TRUE);
         
        $this->token = (string)$token;
        $_SESSION['sb_token'] = $this->token;

        return $this;
    }

    public function request() {

        $response = $this->provider->post('auth', [
            'form_params' => [
                'app_id' => $this->app_id,
                'public' => $this->client_id,
                '_token' => $this->token,
            ]
        ])->getBody(true);

        return $response = (string)$response;
    }

    public function deauthorize() {
        session_destroy();

        unset($this->token);
        unset($this->access_token);
    }
}

By simply calling:

app('auth')->authorize();

This first fetches a csrf token through a get request and adds it to the session as sb_token

Then sends an a post request with app_id and client_id to get an authorization token.

The API returns a token that is encrypted like so:

Crypt::encrypt(md5('app_secret_key'));

Then we request an access token by passing in the the app_id, an md5 string of the secret key, the returned token, the csrf token stored in the session.

This returns an access token that is saved to the session as sb_access_token

This all seems to work fine, but then when I try to make a request for some data:

$request = app('client')->provider();
$response = $request->post('list', [
    'form_params' => [
        'access_token' => $_SESSION['sb_access_token'],
        '_token' => $_SESSION['sb_token'],
    ]
])->getBody(true);

$data = (string)$response;

Once again I'm getting the token mismatch error?

Why does my next request not match my session csrf token?

How do I stop the API from creating a new csrf token on the next request, or more accurately, maintain the state of the current session. Apparently a new session is being created with a new token.

Help!

jimmck's avatar

Hey, You are using CSRF tokens as an authentication mechanism, I really don't think that it what they are intended for. All of this work and you could have used Tymons or other JWT classes. So you are doing all of the same work to maintain JWT for a site token. You are putting the token in the session token. The middleware is looking for it in the POST request. Again why not just use a JWT token on your guarded routes without CSRF? You have already done 90% of the work. I posted a modified example of the middleware that ships with JWT. It does auth and does refresh when it expires. Plus you can extend it to add claims for other things, like maybe what API calls a given user can do. I caveat is in reading Tymons code, I am not sure how much it supports custom claims. Also you need to use out of the box laravel auth.

But it easy to get a CSRF token as you see. But since you are direct connecting via Guzzle you need to maintain session info and such. SO make sure POST data structures contain the Laravel expected pieces like _token and what ever else. From a browser perspective I have a single page app that stays up by using JWT for auth and via JWT protected route the ability to refresh the CSRF token when it expires. I have both tokens expire every two mins to test the App runs perfectly catching the errors and refreshing. good luck, you are clearly thinking all the right stuff. Give yourself all the tools.

secondman's avatar

@jimmck

You are using CSRF tokens as an authentication mechanism

No I'm not. I'm using it as a way to allow a post to my auth route. I can't do a post without a csrf token so I have to get one first. It's not part of the auth routine.

All of this work and you could have used Tymons or other JWT classes.

This isn't up to me, the client wants what the client wants, JWT is not part of the equation so if you can avoid referencing it and help with the current flow that would be great.

make sure POST data structures contain the Laravel expected pieces like _token and what ever else

If you look at the code, my posts do include _token as Lumen expects, the problem is that I'm not understanding how to maintain the state between the api and the sdk. When I make a different post, Lumen is generating a new csrf token so my post with my session loaded csrf token doesn't match.

At this point I'm at the point to turn off the csrf and just use the xcsrf included in the Guzzle client. Doing double verification seems silly.

secondman's avatar

@jimmck

Ok I've taken your advice and installed JWT and low and behold, same problem. I can't post an email and password to get a token because of csrf protection. WTH???

jimmck's avatar

@vkronlein Re-read my first post. You are going round and round, you need to identify and plan a strategy. Me and anybody else reading this only know a little part of your project. What Lumen services do you need? If you have auth in place does it matter where an API request comes from? Sessions, does an API need you to maintain state for them or are all API requests Atomic? For example, your API returns a price or snapshot dataset, then probably not.

secondman's avatar

@jimmck

We have a strategy, thanks. A very well mapped out one. The problem is trying to learn how to get my system to communicate with Lumen and maintain state.

But I have finally figured it out. The reason I was continuing to get the token mis-match was because I hadn't actually logged in the user before getting the csrf token.

So now I'm logging in my user, then generating the JWT with that logged in user, setting the csrf token, and their application token and all is well.

public function accessToken (Request $request) {

    $app = Application::where('identifier', $request->app_id)->first();

    $key = md5($app->keys->test_secret);

    if ($app->live && $request->secret === md5($app->keys->live_secret)):
        $key = md5($app->keys->live_secret);
    endif;
    
    if (Crypt::decrypt($request->token) === $key && $key === $request->secret):
        // create access token
        $crypt = [
            'app_id'        => $app->identifier,
            'client_id'     => $app->live ? $app->keys->live_public : $app->keys->test_public,
            'client_secret' => $app->live ? $app->keys->live_secret : $app->keys->test_secret,
            'user_id'       => $app->user_id
        ];

        // Login the user so we can get accurate tokens
        
        $user = app('auth')->loginUsingId($app->user_id);

        $access_token = Crypt::encrypt($crypt);

        try {
            // attempt to verify the credentials and create a token for the user
            if ( ! $bearer_token = JWTAuth::fromUser($user)):

                return response()->json(['error' => 'invalid_credentials'], 401);

            endif;

        } catch (JWTException $e) {

            // something went wrong whilst attempting to encode the token
            return response()->json(['error' => 'could_not_create_token'], 500);
        }

        $data = [
            'access_token' => $access_token,
            'bearer_token' => $bearer_token,
            'csrf_token'   => csrf_token(),
        ];

        session(['live_site' => $app->live, 'access_token' => $access_token]);

        return response()->json($data);

    else:
        
        return response()->json(['error' => 'invalid client secret'], 600);

    endif;
}

The other main issue was that in Laravel\Lumen\Http\Middleware\VerifyCsrfToken there is no $except property like there is in Laravel. So I had to roll my own and overload handler and add the shouldPassThrough method:

namespace Api\Middleware;

use Closure;
use Illuminate\Session\TokenMismatchException;
use Laravel\Lumen\Http\Middleware\VerifyCsrfToken as BaseVerifier;

class VerifyCsrfToken extends BaseVerifier {

    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        'v1/auth',
        'v1/auth/token'
    ];

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     *
     * @throws \Illuminate\Session\TokenMismatchException
     */
    public function handle($request, Closure $next)
    {
        if (
            $this->isReading($request) || 
            $this->shouldPassThrough($request) || 
            $this->tokensMatch($request)
            ) 
        {
            
            return $this->addCookieToResponse($request, $next($request));
        }

        throw new TokenMismatchException;
    }

    /**
     * Determine if the request has a URI that should pass through CSRF verification.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return bool
     */
    protected function shouldPassThrough($request)
    {
        foreach ($this->except as $except) {
            if ($request->is($except)) {
                return true;
            }
        }

        return false;
    }
}

Hopefully using your custom GetUserFromToken middleware will help keep everything up to date. I think I may need to add some additional stuff for all my additional params that are required for a logged in user.

Thanks for your help, hopefully someone else will find the post useful.

tgif's avatar

great post @vkronlein and @jimmck .

@vkronlein can you explain the sequence of steps your initial request will go through to get access to your api in order to be successful? I've been restling with the csrf token also

jimmck's avatar

@csuarez I was playing around with ways to refresh the CSRF token for a single page web app. I have a GET route that can give you a new token, but its open to any to call and use. So needing to learn a little about JWT did a little experiment with that. So the code you see is not production by any stretch, just something to whet my curiosity as to possible solutions and pitfalls. The GetUserFromAuth is from Tymonns JWT package. He has a refresh middleware piece as well but I was getting errors in using it. So I just modified the auth middleware to do a JWT token refresh when it expired on the Auth check. I also wanted to send the token a header in a more traditional way. I also have simple GET and POST routes to get and refresh the JWT directly from the single page app. @vkronlein seems to be doing something interesting in that he is doing stuff over Guzzle and has to emulate alot of the handshaking that goes on Browser to Laravel and back. But thats up to him to explain. Now that Laravel has begun to add credentials it would be interesting to add custom claims and other things to a JWT token. And of course if you have a token, why bother with CSRF as the token could carry allowed access info rather than session. The one thing I would like is to get reliable control over backend Laravel error handling. Under 4.x I had a good layer of messaging going on and since 5.1 it seems many Exceptions are getting swallowed up. But I was focused on moving to Vue and trying out Ping Pong modules and reworking Controllers. Now I am back to cleaning up the error handling as a single page app needs Exceptions rather than 404 pages.

tgif's avatar

hi @jimmck thanks for the reply.

"I was playing around with ways to refresh the CSRF token for a single page web app."
Why do you want to refresh the token?
"I have a GET route that can give you a new token"
Is that all your GET route does? Or is that when the user first requests the page or within other processes in the page?
"but its open to any to call and use."
Does this go counter to your needs?
"I also have simple GET and POST routes to get and refresh the JWT directly from the single page app."
How are they triggered?
"Now that Laravel has begun to add credentials"
Are you referring to Laravel ACL?
"add custom claims"
??? Sorry I'm really new to this. Do you mean that depending on the role of the user, he/she would be subject to different token type?

thanks

jimmck's avatar

@csuarez

I want to refresh the token so the single page app can continue to send POSTs.

GET routes are not subject CSRF Middleware, so you can do a GET and deliver a new token. I was using JWT token to protect the route.

The call to refresh CSRF involves getting a 500 error and trying to reset the CSRF token. If the error gets cleared the App assumes all is well. Thats why I need to improve Exception tracking.

The JS code make a library call to a Network Command, they are always POST requests.

PayloadMsg.prototype.call = function(url, callData, context) {
    if (this.prefix != "") {
        url = this.prefix + '/' + url;
    }

    this.lastError = NetError.ok;
    this.urlExists(url, this.exists);

    if (this.urlOk == false || url == ""  || arguments.length == 0) {
        fyi("Bad URL or Token [" + url + "]");
        this.getToken();
        if (this.getLastError() == NetError.ok) {
            // check token is ok.
            this.verifyToken();
            if (this.getLastError() == NetError.ok) {
                fyi("New Token [" + this.csrf_token + "]");
            } else {
                fyi("CSRF Token Error " + this.lastStatusCode + " " + HTTPStatus[this.lastStatusCode]);
            }
        } else {
            this.refreshToken();
            if (this.getLastError() == NetError.ok) {
                this.getToken();
                if (this.getLastError() == NetError.ok) {
                    // check token is ok.
                    this.verifyToken();
                    if (this.getLastError() == NetError.ok) {
                        fyi("New Token [" + this.csrf_token + "]");
                    } else {
                        fyi("CSRF Token Error " + this.lastStatusCode + " " + HTTPStatus[this.lastStatusCode]);
                    }
                }
            } else {
                fyi("Refresh Token Error " + this.lastStatusCode + " " + HTTPStatus[this.lastStatusCode]);
            }
        }
    }

    if (this.getLastError() != NetError.ok) {
        return;
    }
... Complete the call

Finally Yes store other App specific info in the Token asm opposed to the session. Some Reading... Enjoy!

https://auth0.com/docs/jwt

http://blog.nedex.io/create-an-api-server-for-mobile-apps-using-laravel-5-1/

http://www.toptal.com/web/cookie-free-authentication-with-json-web-tokens-an-example-in-laravel-and-angularjs

tgif's avatar

@jimmck i c. Thank you, jimmck. Need to do some more research before tackling this capability.

Please or to participate in this conversation.