Mark Grealish

the life and code of a crazy cat man


Six Sunny Sunset Skies

in ireland

Taken around Taylor’s Lane, Prospect Avenue and Smithfield this evening.

Sunset Rooftops in Dublin
Sunset Rooftops in Dublin
Sunset Rooftops in Dublin
Sunset Rooftops in Dublin
Sunset Rooftops in Dublin
Sunset Rooftops in Dublin

Eight!

in family

Garrett on his eighth birthday

Gar looks so like me sometimes. <3 I hung out with Mariah, Caira and the rest of them on Friday for about thirty minutes. In the middle of dinner at Denny’s. It was fun!


instasort 4.0.1

in code

One bad programmer habit of mines is that I tend to duck type when there isn’t a strict need. I ask whether the object doesn’t fit conditions, or evaluate it against some rules, at times when I have the ability to check its actual type. I thusly duck typed images in the previous version of my instasort shell script when I asked: is the image square? Instagram images are square, therefore a square image is an Instagram image.

i r logic

My assumptions led to a whole bunch of false positives in results, because Instagram images are no longer square, and sometimes I take square images anyhow. I poked into the problem this morning and discovered I could test the exif:Software key:

# Was File Created by Instagram?
#
# @example
#
#   exif:Software: Instagram
#   exif:Software: Layout from Instagram
#   exif:Software: 10.3.1
#
# Files created by Instagram will be marked so in EXIF. I downcase the exif:software
# value and test it against 'instagram'.

function is_instagram {
    local SOFTWARE=$(identify -format '%[exif:software]' $1)
    [[ ${SOFTWARE:l} =~ 'instagram' ]]
    echo $?
}

So there you have it: assumptions are bad. You can find the less-assuming version of instasort here on GitHub.


Double Rainbow over School Street, Dublin

in ireland

Double rainbow in Dublin 8

Invalid Authenticity Token Errors in Rails 5

in code

Over the last while we had persistent

ActionController::InvalidAuthenticityToken

(CSRF) token issues in our natively-wrapped application. The error only appeared to occur under specific circumstances:

  1. Open app.
  2. Perform any action, then logout.
  3. Log back in again. All remote POST/PUT/DELETE requests will fail with token errors.

Typical trace:

Started POST "/events/147/attendances" for 127.0.0.1 at 2017-09-12 12:17:54 +0000
Processing by AttendancesController#create as JS
  Parameters: {"authenticity_token"=>"kt/qu7KBT2ZLVTBs5ccuHubymW0pEsYPss86jtmY2u6qPf+CIilMJooWLGTEEd0rfx6/Q0ZvT7kG0JRl6LxySg==", "event_id"=>"147"}
Can't verify CSRF token authenticity.
Completed 422 Unprocessable Entity in 1ms (ActiveRecord: 0.0ms)

ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):

actionpack (5.1.2) lib/action_controller/metal/request_forgery_protection.rb:195:in `handle_unverified_request'
actionpack (5.1.2) lib/action_controller/metal/request_forgery_protection.rb:227:in `handle_unverified_request'
...

A hard reload would restore app functionality in all cases. It took me three days of dedicated work to trace the problem, write a fix, and deploy the fix to production. (╯°□°)╯︵ ┻━┻

Load Balancer/Traces

Our first thought was that maybe it was a load balancer issue? Did one instance issue a token which another instance considered invalid? That led me to dive into how Rails validates a CSRF token, by way of stepping through the source file. I copied the file to our config/initializers/ folder, inserted a bunch of logger statements, and walked the token through the file.

Pathways in Phoenix Park, Dublin

Nothing. Like, sure, now I know what happens when I insert before_action :verify_authenticity_token into a controller, but that didn’t fix my problem. Traces from the error weren’t helpful either, because the trace only goes as far back as line 195, which can be called from several places in the file.

Turbolinks

Oh boy. Turbolinks acts as the backbone of our application’s view layer, and CSRF issues are an old, known problem with the library. Comparison of the expected (issued with form_authenticity_token) dumped to server logs, with the token set in the browser, showed me the root cause of the problem: the CSRF tokens set in (i) the header meta tag, and (ii) AJAX request headers, were both incorrect. Neither matched the issued token.

So why? Subsequent POST/PUT/DELETE actions would work after I hand-set the new token, but why wasn’t it set correctly? What happens normally:

  1. A new token is issued on each non-XHR GET request.
  2. The token is set in a header meta tag.
  3. The jquery-rails gem will read the new tag and update AJAX headers appropriately. The update fires (looks like) on each turbolinks:load event.

Step #2 fails and I still don’t know why. ¯\_(ツ)_/¯

Caching and Shite

Specifically, the error occurs only in our native app wrapper. As far as I can tell, either the app wrapper or the browser continues to cache parts of the older header during a HTTP 302 redirect.


Fixing the Problem/Single Source of Truth

With the problem (the old CSRF token persists) and cause (caching stuff?) established, I set out to fix the problem. Hours of trial and error and Google left me in favour of either a request response or a non-secure cookie to hold the token. Other programmers have resorted to the same solution.

While other developers favoured adding a new token to each response request through a custom, I didn’t like that each request authorizes the next, in essence. There’s no single source of truth in the page for the CSRF token. I hold that authorization to make actions should rest with the document/page/cookies instead. Fastly advocates the use of a secure cookie to hold the CSRF token. Good idea, except that the token doesn’t need to be secure because I have to expose the token to the client for it to make a successful request.

Fix: Application Controller

I added code to our application controller, such that when the site receives a valid non-XHR GET request, it’ll create a non-secure token which contains the new CSRF token.

class ApplicationController < ActionController::Base
  before_action :set_csrf_token, if: :valid_get_request?

  private

  def valid_get_request?
    protect_against_forgery? && !request.xhr? && request.get?
  end

  def set_csrf_token
    cookies[:csrf_token] = {
      value: form_authenticity_token,
      expires: 5.minutes.from_now,
      secure: false
    }
  end
end

Fix: document.cookie

The next thing I needed was the ability to parse and extract values from the document.cookie string. We use Lodash, which made my life way easier:

App.cookies = App.cookies || {
    get: function(cookie) {
        const cookies = _(document.cookie).split(';').map(this._splitCookie).fromPairs().value();

        if (cookie) {
            return _.get(cookies, cookie);
        } else {
            return cookies;
        }
    },

    _splitCookie: function(cookie) {
        return cookie.split('=').map(ck => ck.trim());
    }
};

The function will return either all non-secure cookies, the specified cookie key if it exists, or undefined if the key isn’t set:

App.cookies.get(); // { csrf_token: 'abc123' }
App.cookies.get('csrf_token'); // 'abc123'
App.cookies.get('sexy_pony_hooves'); // undefined

Fix: Set CSRF Token

The final step is to set the new CSRF token after I extract it from the cookie. We use Backbone.js-style event listeners. I devolve control to each script: they push their own handlers to the global stack. One thing I noticed is that the CSRF token is percent-encoded, and so must be run thorugh decodeURIComponent before I can use it.

Note: decodeURIComponent has no fucks to give: It will cast undefined as 'undefined'. The code first extracts the cookie-token and tests for truthiness before any further handling.

App.tokens = App.tokens || {
    ajax: function(newToken) {
        if (newToken) {
            $.ajaxSetup({
                headers: { 'X-CSRF-Token': newToken }
            });
        }

        return _.get($.ajaxSettings, 'headers.X-CSRF-Token');
    },

    html: function(newToken) {
        const meta = $('meta[name="csrf-token"]');

        if (newToken) {
            meta.attr('content', newToken);
        }

        return meta.attr('content');
    },

    set: function(newToken) {
        App.tokens.ajax(newToken);
        App.tokens.html(newToken);
    },

    listeners: {
        'turbolinks:load': function() {
            const token = App.cookies.get('csrf_token');

            if (token) {
                App.tokens.set(decodeURIComponent(token));
            }
        }
    }
};

App.listeners.add(App.tokens.listeners);

Last Word

My life would be easier without any of this code-y stuff. The $.rails.refreshCSRFTokens() method provided by jquery-ujs doesn’t help at all because it sets form element tags based on the meta tag.

> $.rails.refreshCSRFTokens
ƒ (){e('form input[name="'+n.csrfParam()+'"]').val(n.csrfToken())}

Wrong meta tag information, wrong form information.


Flogging Molly at the Olympia

in ireland

Flogging Molly

What a fantastic gig, full of great craic! Mariah introduced me to Flogging Molly years ago when I lived in Las Vegas. They’ve stayed a firm favourite of mine since. Flogging Molly’s songs are of home and loss, of survival and endurance. About where you are and what family means. It meant a great deal to to me to see Float performed live. There’s a song that kept me going through the worst moments of my life.