2

I've learned in this SO question that there currently is no simple way to turn cookie-based CSRF tokens into HTTP request headers in Elm. Thus, to write a single page application (SPA) that works nicely with a Django Rest Framework backend, I need to manually retrieve the CSRF-Token from the corresponding cookie value.

How do I retrieve a cookie value in Elm? Does Elm provide runtime support for this via some Command? Or do I need to retrieve the cookie using plain JavaScript and provide it to the ELM SPA via a port?

Ulrich Schuster
  • 1,670
  • 15
  • 24

2 Answers2

4

As of Elm 0.9, you need to use Ports to read the cookie from JavaScript and pass it back to the Elm application.

In my application, I do the following. I define a fetchCsrfToken port that I use from Elm to call a JavaScript function that reads the cookie. That function then triggers a callback to Elm via a csrfTokenReciever port. My Elm application subscribes to that event via subscriptions.

-- Ports.elm

port fetchCsrfToken : () -> Cmd msg
port csrfTokenReciever : (String -> msg) -> Sub msg
-- Main.elm

init : Flags -> Url -> Key -> ( Model, Cmd Msg )
init flags url key =
  -- ...
  (model, Ports.fetchCsrfToken ())
  

subscriptions : Model -> Sub Msg
subscriptions model =
  Ports.csrfTokenReciever GotCsrfToken
// index.js

app.ports.fetchCsrfToken.subscribe(function (str) {
  const value = getCookie('csrftoken')
  if (value === null) {
    app.ports.csrfTokenReciever.send('')
  } else {
    app.ports.csrfTokenReciever.send(value)
  }
})
viam0Zah
  • 25,949
  • 8
  • 77
  • 100
  • To my understanding, I can use Ports to send data from Elm to JS, but I need a Subscription to funnel data from JS to Elm - or am I missing something? I can see two ways to do that: In JS, write a listener on cookie changes that sends data to an Elm subscription. Or write a JS function that gets triggered via an Elm Port, and which in turn writes data via a subscription back to Elm. Correct? Which one should I use? – Ulrich Schuster Dec 04 '21 at 20:24
  • 1
    @UlrichSchuster you're correct, you will also need to use the subscriptions. I follow the second method you described. I've extended my answer with an example code. – viam0Zah Dec 05 '21 at 21:18
1

Using Elm 0.19.1

First solution:

Use of 2 ports, a subscription and some JS/TS code like @viam0Zah mentioned.


Second solution:

Pass the CSRF into your flags at init

    const app = Elm.Main.init({
      node: document.querySelector("main"),
      flags: {
        csrfToken: getCookie('csrftoken')
      }
    });

add csrfToken to Flags

type alias Flags =
    { ---
    , csrfToken : String
    }

And don't forget to add a decoder for the csrfToken:

import Json.Decode as D

flagsDecoder : D.Decoder Flags
flagsDecoder =
    D.succeed Flags
        |> ---
        |> D.required "csrfToken" D.string

If you want to be more robust and extend type safety for both solutions - flags and ports, you should check out https://elm-ts-interop.com/, it's just amazing!

Charlon
  • 363
  • 2
  • 16