developers

Creating Your First Elm App: From Authentication to Calling an API (Part 2)

Explore building an app in the functional front-end language Elm. Part 2 focuses on adding authentication to an Elm App.

Update:

This article and its supporting GitHub repos have been updated to Elm v0.18!

TL;DR: In the first part of this tutorial, we introduced the Elm language by building a simple Elm Application that called an API. Now we'll authenticate with JSON Web Tokens to make protected API requests. The full code is available in this GitHub repository.


Elm is a functional language that compiles to JavaScript. Its robust compiler and static typing make it a nice option for developing applications for the browser that are free of runtime errors. In the first part of this Elm tutorial, we built a small web app to introduce the language and its syntax.

Authenticating the Chuck Norris Quoter App

Now we'll continue to build out our Chuck Norris Quoter app to add user registration, login, and make authenticated API requests with JSON Web Tokens. We'll also use JavaScript interop to persist user sessions with local storage.

Registering a User

Last time, we finished up by retrieving Chuck Norris quotes from the API. We also need registration so users can be issued access tokens with which to access protected quotes. We'll create a form that submits a

POST
request to the API to create a new user and return an access token.

elm quote

After the user has registered, we'll display a welcome message:

elm quote

Here's the complete

Main.elm
code for this step:

src/Main.elm - Step 3: Registering a User

We'll start with new imports:

import Json.Decode as Decode exposing (..)
import Json.Encode as Encode exposing (..)

We'll be sending objects and receiving JSON now instead of just working with a string API response, so we need to be able to translate these from and to Elm records.

{-
    MODEL
    * Model type
    * Initialize model with empty values
    * Initialize with a random quote
-}

type alias Model =
    { username : String
    , password : String
    , token : String
    , quote : String
    , errorMsg : String
    }

init : (Model, Cmd Msg)
init =
    ( Model "" "" "" "" "", fetchRandomQuoteCmd )

Our model needs to hold more data than just a quote now. We've added

username
,
password
,
token
, and
errorMsg
(to display any API errors from authentication).

We'll initialize our application with empty strings for all of the above.

{-
    UPDATE
    * API routes
    * GET and POST
    * Encode request body
    * Decode responses
    * Messages
    * Update case
-}

-- API request URLs  

...

registerUrl : String
registerUrl =
    api ++ "users"

The API route for

POST
ing new users is http://localhost:3001/users so we'll create
registerUrl
to store this.

-- Encode user to construct POST request body (for Register and Log In)

userEncoder : Model -> Encode.Value
userEncoder model =
    Encode.object
        [ ("username", Encode.string model.username)
        , ("password", Encode.string model.password)
        ]

The API expects the request body for registration and login to be a JavaScript object in string format that looks like this:

{ username: "string", password: "string" }

An Elm record is not the same as a JavaScript object so we need to encode the applicable properties of our model before we can send them with the HTTP request. The

userEncoder
function utilizes
Json.Encode
to take the model and return the encoded value.

-- POST register / login request


authUser : Model -> String -> Http.Request String
authUser model apiUrl =
    let
        body =
            model
                |> userEncoder
                |> Http.jsonBody
    in
        Http.post apiUrl body tokenDecoder

We'll use a

POST
HTTP request this time as opposed to the simple
getString
used for the quote in the previous step. The same settings can be used for both register and login with the exception of the API route which we'll pass in an argument.

We'll call this effect function

authUser
(for "authenticate a user"). The type says "
authUser
takes model as an argument and a string as an argument and returns a request that succeeds with a string".

Let's take a closer look at these lines:

...
        body =
            model
                |> userEncoder
                |> Http.jsonBody

<|
and
|>
are aliases for function application to reduce parentheses.
<|
is backward function application
.

body = model <| userEncoder <| Http.jsonBody
takes the results of the last function and passes it as the last argument to the function on its left. Written with parentheses, the equivalent would be:
body = model (userEncoder (Http.jsonBody))
.

We'll run the

userEncoder
function to encode the request body.

Next we want to send the HTTP

POST
request with the API URL and then take the JSON result and decode it with a
tokenDecoder
function that we'll create in a moment.

We now have our

authUser
effect so we need to create an
authUserCmd
command. This should look familiar from fetching quotes earlier. We're also passing the API route as an argument. We'll create a
GetTokenCompleted
message to handle errors and successes as well.

authUserCmd : Model -> String -> Cmd Msg
authUserCmd model apiUrl =
    Http.send GetTokenCompleted (authUser model apiUrl)


getTokenCompleted : Model -> Result Http.Error String -> ( Model, Cmd Msg )
getTokenCompleted model result =
    case result of
        Ok newToken ->
            ( { model | token = newToken, password = "", errorMsg = "" } |> Debug.log "got new token", Cmd.none )

        Err error ->
            ( { model | errorMsg = (toString error) }, Cmd.none )

In the

getTokenCompleted
function's
Ok
case, we've authenticated the user now, so we can clear the password and any errors. This is a good place to verify that everything is working as expected, so let's log the updated model to the browser console using the
|>
forward function application alias and a
Debug.log
:
{ model | token = newToken, password = "", errorMsg = "" } |> Debug.log "got new token"
.

Note: After verifying our expectations in the browser console, we should remove the

Debug.log
.

We'll also define the

tokenDecoder
function that ensures we can work with the response from the HTTP request:

-- Decode POST response to get access token


tokenDecoder : Decoder String
tokenDecoder =
    Decode.field "access_token" Decode.string

When registering or logging in a user, the response from the API is JSON shaped like this:

{
    "access_token": "someJWTTokenString",
    "id_token": "someJWTTokenString"
}

ID tokens should only be used on the client side. We'll decode the

access_token
to extract its contents as a string that will be returned on success. The access token is the one that we need in order to make authorized API requests.

We want to display authentication errors to the user. Unlike the error case we implemented earlier,

getTokenCompleted
's
Err
won't discard its argument. The type of the
error
argument is
Http.Error
. This is a union type that could be a few different errors. For the sake of simplicity, we're going to convert the error to a string and update the model's
errorMsg
with that string.

Note: A good exercise later would be to translate the different errors to more user-friendly messaging.

Now we will set up a way for our UI to interact with the model:

-- Messages

type Msg
    ...
    | SetUsername String
    | SetPassword String
    | ClickRegisterUser
    | GetTokenCompleted (Result Http.Error String)



-- Update


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        ...  

        SetUsername username ->
            ( { model | username = username }, Cmd.none )

        SetPassword password ->
            ( { model | password = password }, Cmd.none )

        ClickRegisterUser ->
            ( model, authUserCmd model registerUrl )

        GetTokenCompleted result ->
            getTokenCompleted model result

The

SetUsername
and
SetPassword
messages are for sending form field values to update the model.
ClickRegisterUser
is the
onClick
for our "Register" button. It runs the
authUserCmd
command we just created and passes the model and the API route for new user creation.

GetTokenCompleted
is the response handling function for the
authUser
request. Its argument is the token string result. We'll update our model with the token so we can use it to request protected quotes later.

{-
    VIEW
    * Hide sections of view depending on authenticaton state of model
    * Get a quote
    * Register
-}


view : Model -> Html Msg
view model =
    let
        -- Is the user logged in?
        loggedIn : Bool
        loggedIn =
            if String.length model.token > 0 then
                True
            else
                False

        -- If the user is logged in, show a greeting; if logged out, show the login/register form
        authBoxView =
            let
                -- If there is an error on authentication, show the error alert
                showError : String
                showError =
                    if String.isEmpty model.errorMsg then
                        "hidden"
                    else
                        ""

                -- Greet a logged in user by username
                greeting : String
                greeting =
                    "Hello, " ++ model.username ++ "!"
            in
                if loggedIn then
                    div [ id "greeting" ]
                        [ h3 [ class "text-center" ] [ text greeting ]
                        , p [ class "text-center" ] [ text "You have super-secret access to protected quotes." ]
                        ]
                else
                    div [ id "form" ]
                        [ h2 [ class "text-center" ] [ text "Log In or Register" ]
                        , p [ class "help-block" ] [ text "If you already have an account, please Log In. Otherwise, enter your desired username and password and Register." ]
                        , div [ class showError ]
                            [ div [ class "alert alert-danger" ] [ text model.errorMsg ]
                            ]
                        , div [ class "form-group row" ]
                            [ div [ class "col-md-offset-2 col-md-8" ]
                                [ label [ for "username" ] [ text "Username:" ]
                                , input [ id "username", type_ "text", class "form-control", Html.Attributes.value model.username, onInput SetUsername ] []
                                ]
                            ]
                        , div [ class "form-group row" ]
                            [ div [ class "col-md-offset-2 col-md-8" ]
                                [ label [ for "password" ] [ text "Password:" ]
                                , input [ id "password", type_ "password", class "form-control", Html.Attributes.value model.password, onInput SetPassword ] []
                                ]
                            ]
                        , div [ class "text-center" ]
                            [ button [ class "btn btn-link", onClick ClickRegisterUser ] [ text "Register" ]
                            ]
                        ]
    in
        div [ class "container" ]
            [ h2 [ class "text-center" ] [ text "Chuck Norris Quotes" ]
            , p [ class "text-center" ]
                [ button [ class "btn btn-success", onClick GetQuote ] [ text "Grab a quote!" ]
                ]
              -- Blockquote with quote
            , blockquote []
                [ p [] [ text model.quote ]
                ]
            , div [ class "jumbotron text-left" ]
                [ -- Login/Register form or user greeting
                  authBoxView
                ]
            ]

There's a lot of new stuff in the view but it's mostly form HTML. We'll start with some logic to hide the form once the user is authenticated; we want to show a greeting in this case.

Remember that

view
is a function. This means we can do things like create scoped variables with
let
expressions
to conditionally render parts of the view.

For the sake of simplicity, we'll check if the model's token string has a length to determine if the user is logged in. In a real-world application, token verification might be performed in a route callback to ensure proper UI access. For our Chuck Norris Quoter, the token is needed to get protected quotes so all the

loggedIn
variable does is show the form versus a simple greeting.

We'll then create the

authBoxView
. This contains the form and greeting and executes either depending on the value of
loggedIn
. We'll also display the authentication error if there is one.

If the user is logged in, we'll greet them by their username and inform them that they have super-secret access to protected quotes.

If the user is not logged in, we'll display the Log In / Register form. We can use the same form to do both because they share the same request body. Right now though, we only have the functionality for Register prepared.

After the heading, instructional copy, and conditional error alert, we need the

username
and
password
form fields. We can supply various attributes:

input [ id "username", type_ "text", class "form-control", Html.Attributes.value model.username, onInput SetUsername ] []
...
input [ id "password", type_ "password", class "form-control", Html.Attributes.value model.password, onInput SetPassword ] []

There are a few things that may stand out here:

type_
has an underscore after it because
type
is a reserved keyword.
Html.Attributes.value
is fully qualified because
value
alone is ambiguous in context because the compiler could confuse it with
Json.Decode.value
.
onInput
is a custom event handler that gets values from triggered events. When these events are fired we want to update the username or password in the model.

After our form fields, we'll include a "Register" button with

onClick ClickRegisterUser
. We'll use Bootstrap's CSS to style this button like a link since it will live next to a Log In button later.

Finally, we'll use our

authBoxView
variable in the main view. We'll place it below our Chuck Norris quote in a jumbotron.

Now we can register new users in our app. When successfully registered, the user will receive a token and be authenticated. The view will then update to show the greeting message. Try it out in the browser. You should also try to trigger an error message!

Logging In and Logging Out

Now that users can register, they need to be able to log in with existing accounts.

elm authentication quote log in

We also need the ability to log out.

elm authentication quote log out

The full

Main.elm
code with login and logout implemented will look like this:

src/Main.elm - Logging In and Logging Out

Login works like Register (and uses the same request body), so creating its functionality should be straightforward.

-- API request URLs

...

loginUrl : String
loginUrl =
    api ++ "sessions/create"

We'll add the login API route, which is http://localhost:3001/sessions/create.

We already have the

authUser
effect and
authUserCmd
command, so all we need to do is create a way for login to interact with the UI. We'll also create a logout.

-- Messages


type Msg
    ...
    | ClickLogIn
     ...
    | LogOut



-- Update


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
         ...

        ClickLogIn ->
            ( model, authUserCmd model loginUrl )

        ...

        LogOut ->
            ( { model | username = "", token = "" }, Cmd.none )

ClickLogIn
runs the
authUserCmd
command with the appropriate arguments.
LogOut
resets authentication-related data in the model record to empty strings. We don't need to reset the
password
or
errorMsg
because we already did so when we successfully retrieved a token in
GetTokenCompleted
.

...

if loggedIn then
    div [id "greeting" ][
        h3 [ class "text-center" ] [ text greeting ]
        , p [ class "text-center" ] [ text "You have super-secret access to protected quotes." ]
        , p [ class "text-center" ] [
            button [ class "btn btn-danger", onClick LogOut ] [ text "Log Out" ]
        ]   
    ]

...                    

, div [ class "text-center" ] [
    button [ class "btn btn-primary", onClick ClickLogIn ] [ text "Log In" ]
    , button [ class "btn btn-link", onClick ClickRegisterUser ] [ text "Register" ]
]

...

There are minimal updates to the view. We'll add a logout button in the greeting message in

authBoxView
. Then in the form we'll insert the login button before the register button.

Registered users can now log in and log out. Our application is really coming together!

Note: A nice enhancement might be to show different forms for logging in and registering. Maybe the user should be asked to confirm their password when registering?

Getting Protected Quotes

It's time to make authorized requests to the API to get protected quotes for authenticated users. Our logged out state will look like this:

elm authentication get quote

If a user is logged in, they'll be able to click a button to make API requests to get protected quotes:

elm authentication get protected quote

Here's the completed

Main.elm
code for this step:

src/Main.elm - Getting Protected Quotes

Let's update our model:

{-
    MODEL
    * Model type
    * Initialize model with empty values
    * Initialize with a random quote
-}

type alias Model =
    { username : String
    , password : String
    , token : String
    , quote : String
    , protectedQuote : String
    , errorMsg : String
    }

init : (Model, Cmd Msg)
init =
    ( Model "" "" "" "" "" "", fetchRandomQuoteCmd )

We're adding a

protectedQuote
property to the Model type alias. This will be a string. We'll add another pair of double quotes
""
to the
init
tuple to initialize our app with an empty string for the protected quote.

-- API request URLs

...  

protectedQuoteUrl : String
protectedQuoteUrl =
    api ++ "api/protected/random-quote"

Add the API route for the

protectedQuoteUrl
: http://localhost:3001/api/protected/random-quote.

-- GET request for random protected quote (authenticated)


fetchProtectedQuote : Model -> Http.Request String
fetchProtectedQuote model =
    { method = "GET"
    , headers = [ Http.header "Authorization" ("Bearer " ++ model.token) ]
    , url = protectedQuoteUrl
    , body = Http.emptyBody
    , expect = Http.expectString
    , timeout = Nothing
    , withCredentials = False
    }
        |> Http.request


fetchProtectedQuoteCmd : Model -> Cmd Msg
fetchProtectedQuoteCmd model =
    Http.send FetchProtectedQuoteCompleted (fetchProtectedQuote model)


fetchProtectedQuoteCompleted : Model -> Result Http.Error String -> ( Model, Cmd Msg )
fetchProtectedQuoteCompleted model result =
    case result of
        Ok newQuote ->
            ( { model | protectedQuote = newQuote }, Cmd.none )

        Err _ ->
            ( model, Cmd.none )

We'll create the HTTP request to

GET
the protected quote. The type for this request is "
fetchProtectedQuote
takes model as an argument and returns an HTTP request that succeeds with a string". We'll use a fully qualified HTTP request and define an
Authorization
header, an empty body, and expect a string response. The value of our authorization header is
Bearer
plus the user's token string. We then
Http.request
our
POST
request.

Next is the

fetchProtectedQuoteCmd
function. We're quite familiar with these commands now and there are no surprises here.

Finally, we'll implement the

FetchProtectedQuoteCompleted
message. Again, this is very similar to our previous HTTP completed functions. We'll update the model with the quote string from the API on success and discard the argument for an error.

-- Messages

type Msg
    ...
    | GetProtectedQuote
    | FetchProtectedQuoteCompleted (Result Http.Error String)
    ...

-- Update

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        ...

        GetProtectedQuote ->
            ( model, fetchProtectedQuoteCmd model )

        FetchProtectedQuoteCompleted result ->
            fetchProtectedQuoteCompleted model result  

        ...

There are no new concepts in the implementation of the messages here.

GetProtectedQuote
returns the command and
FetchProtectedQuoteCompleted
updates the model.

    -- If user is logged in, show button and quote; if logged out, show a message instructing them to log in
        protectedQuoteView =
            let
                -- If no protected quote, apply a class of "hidden"
                hideIfNoProtectedQuote : String
                hideIfNoProtectedQuote =
                    if String.isEmpty model.protectedQuote then
                        "hidden"
                    else
                        ""
            in
                if loggedIn then
                    div []
                        [ p [ class "text-center" ]
                            [ button [ class "btn btn-info", onClick GetProtectedQuote ] [ text "Grab a protected quote!" ]
                            ]
                          -- Blockquote with protected quote: only show if a protectedQuote is present in model
                        , blockquote [ class hideIfNoProtectedQuote ]
                            [ p [] [ text model.protectedQuote ]
                            ]
                        ]
                else
                    p [ class "text-center" ] [ text "Please log in or register to see protected quotes." ]
    in
        div [ class "container" ]
            [ h2 [ class "text-center" ] [ text "Chuck Norris Quotes" ]
            , p [ class "text-center" ]
                [ button [ class "btn btn-success", onClick GetQuote ] [ text "Grab a quote!" ]
                ]
              -- Blockquote with quote
            , blockquote []
                [ p [] [ text model.quote ]
                ]
            , div [ class "jumbotron text-left" ]
                [ -- Login/Register form or user greeting
                  authBoxView
                ]
            , div []
                [ h2 [ class "text-center" ] [ text "Protected Chuck Norris Quotes" ]
                  -- Protected quotes
                , protectedQuoteView
                ]
            ]

In the

let
, we'll add a
protectedQuoteView
under the
authBoxView
variable. We'll use a variable called
hideIfNoProtectedQuote
with an expression to output a
hidden
class to the
blockquote
. This will prevent the element from being shown if there is no quote.

We'll represent logged in and logged out states using the

loggedIn
variable we declared earlier. When logged in we'll show a button to
GetProtectedQuote
and the quote. When logged out we'll show a paragraph with copy telling the user to log in or register.

At the bottom of our

view
function, we'll add a
div
with a heading and our
protectedQuoteView
.

Check it out in the browser—our app is almost finished!

Persisting Logins with Local Storage

We have the primary functionality done now. Our app gets quotes, allows registration, login, and gets authorized quotes. The last thing we'll do is persist logins.

We don't want our logged-in users to lose their data if they refresh their browser or leave and come back. To do this we'll implement

localStorage
with Elm using JavaScript interop. This is a way to take advantage of features of JS in Elm code. After all, Elm compiles to JavaScript so it only makes sense that we would be able to do this.

When we're done, our completed

Main.elm
will look like this:

src/Main.elm - Persisting Logins with Local Storage

The first things you may notice are changes to our

Main
module and program and
init
:

...

main : Program (Maybe Model) Model Msg
main =
    Html.programWithFlags
        { init = init
        , ...

...

init : Maybe Model -> ( Model, Cmd Msg )
init model =
    case model of
        Just model ->
            ( model, fetchRandomQuoteCmd )

        Nothing ->
            ( Model "" "" "" "" "" "", fetchRandomQuoteCmd )

...

We need to switch from

program
to
programWithFlags
. The type therefore changes from
Program Never
to
Program (Maybe Model) Model Msg
. This means we might have a model provided at initialization. If the model is already in local storage it will be available. If we don't have anything stored when we arrive we'll initialize without it.

We also need to update

init
and its type annotation to handle the fact that the app may be initializing with a model (
Maybe Model
). If there's data present from local storage, we'll set the model. If there isn't, we'll initialize the same way we did previously.

So where does this potential initial model come from? We need to write a little bit of JavaScript in our

src/index.html
's
<script>
tag:

...    
    var storedState = localStorage.getItem('model');
    var startingState = storedState ? JSON.parse(storedState) : null;
    var elmApp = Elm.Main.fullscreen(startingState);

    elmApp.ports.setStorage.subscribe(function(state) {
        localStorage.setItem('model', JSON.stringify(state));
    });

    elmApp.ports.removeStorage.subscribe(function() {
        localStorage.removeItem('model');
    });
...

There is no Elm here. We will use JavaScript to check local storage for previously saved

model
data. Then we'll establish the
startingState
in a ternary that checks
storedState
for model data. If data is found we'll
JSON.parse
it and pass it to our Elm app. If there is no model yet, we'll pass
null
.

Then we need to set up ports so we can use features of

localStorage
in our Elm code. We'll call one port
setStorage
and subscribe to it so we can do something with messages that come through the port. When
state
data is sent we'll use the
setItem
method to set a
model
and save the stringified data to
localStorage
. The
removeStorage
port will remove the
model
item from
localStorage
. We'll use this when logging out.

Now we'll go back to

Main.elm
:

-- Helper to update model and set localStorage with the updated model


setStorageHelper : Model -> ( Model, Cmd Msg )
setStorageHelper model =
    ( model, setStorage model )

We need a helper function of a specific type to save the model to local storage in multiple places in our

update
. Because the
update
type always expects a tuple with a model and command message returned, we need our helper to take the model as an argument and return the same type of tuple. We'll understand how this fits in a little more in a moment.

fetchRandomQuoteCompleted model result =
    case result of
        Ok newQuote ->
            setStorageHelper { model | quote = newQuote }

...

getTokenCompleted model result =
    case result of
        Ok newToken ->
            setStorageHelper { model | token = newToken, password = "", errorMsg = "" }

...

fetchProtectedQuoteCompleted model result =
    case result of
        Ok newPQuote ->
            setStorageHelper { model | protectedQuote = newPQuote }

...

-- Ports


port setStorage : Model -> Cmd msg


port removeStorage : Model -> Cmd msg

...

update msg model =
    case msg of ...
        LogOut ->
            ( { model | username = "", token = "" }, removeStorage model )

We need to define the type annotation for our

setStorage
and
removeStorage
ports. They'll take a model and return a command. The lowercase
msg
is significant because this is an effect manager and its type is actually
Cmd a
. It does not send messages back to the program. Keep in mind that using
Cmd Msg
here will result in a compiler error.

Finally, we're going to go up and replace some of our

Ok
returns in our HTTP completed functions with the
setStorageHelper
. We'll also use the
removeStorage
command for logging out. This helper will return the tuple that our
update
function expects from all branches so we won't have to worry about type mismatches.

We will call

setStorageHelper
and pass the model updates that we want to propagate to the app and save to local storage. We're saving the model to storage when the user is successfully granted a token and when they get a protected quote. On logout, we'll remove the
localStorage
model
item.

Now when we authenticate, local storage will keep our data so when we refresh or come back later, we won't lose our login state or our latest protected quote.

If everything compiles and works as expected, we're done with our basic Chuck Norris Quoter application!

Aside: Using Auth0 with Elm

Auth0 provides registration, login, and authentication with JSON Web Tokens. We can add Auth0 authentication to an Elm app by setting up a couple of additional Elm modules and importing them for use in an app's

Main.elm
file. Marcus Griep has a great Elm Workshop with 0.17 available on GitHub that includes an Auth0.elm module and an Authentication.elm module.

These modules are great resources to help you implement Auth0 with Elm. We will introduce and build on them in a new app to demonstrate how to use auth0.js with JSON Web Tokens. The modules will be imported into our

Main.elm
file and we'll use JS interop to interface with Auth0's Centralized Login Page.

Auth0 centralized login screen

You can download the complete code for the Elm app with Auth0 integration at this GitHub repo.

Important security note: In this demo, we're adding authentication to the client side but we are not securing a backend. If you have an API for your application, the API should always be secured. You can do this with the token provided by Auth0.

Sign Up for Auth0

The first thing we'll need is an Auth0 account. Follow these simple steps to get started:

  1. Sign up for a free Auth0 account.
  2. In your Auth0 Dashboard, create a new client.
  3. Name your new app and select "Single Page Web Applications".
  4. In the Settings for your newly created app, add
    http://localhost:8888
    to the Allowed Callback URLs.

Auth0.js Interop and Local Storage

In our

src/index.html
file, we'll use JavaScript to implement ports that call the Auth0
authorize()
endpoint to log in, and log out:

<!-- index.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <title>Elm with Auth0</title>
    <script src="Main.js"></script>
    <script src="Auth0.js"></script>
    <script src="Authentication.js"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
  </head>
  <body>
  </body>
  <script src="https://cdn.auth0.com/js/auth0/9.0.0/auth0.min.js"></script>
  <script>
    var webAuth = new auth0.WebAuth({
      domain: '[YOUR_AUTH0_DOMAIN]', // e.g., you.auth0.com
      clientID: '[YOUR_AUTH0_CLIENT_ID]',
      scope: 'email',
      responseType: 'token',
      redirectUri: 'http://localhost:8888'
    });
    var storedProfile = localStorage.getItem('profile');
    var storedToken = localStorage.getItem('token');
    var authData = storedProfile && storedToken ? { profile: JSON.parse(storedProfile), token: storedToken } : null;
    var elmApp = Elm.Main.fullscreen(authData);
    // Auth0 authorize subscription
    elmApp.ports.auth0authorize.subscribe(function(opts) {
      webAuth.authorize();
    });
    // Log out of Auth0 subscription
    elmApp.ports.auth0logout.subscribe(function(opts) {
      localStorage.removeItem('profile');
      localStorage.removeItem('token');
    });
    // Watching for hash after redirect
    webAuth.parseHash({ hash: window.location.hash }, function(err, authResult) {
      if (err) {
        return console.error(err);
      }
      if (authResult) {
        webAuth.client.userInfo(authResult.accessToken, function(err, profile) {
          var result = { err: null, ok: null };
          var token = authResult.accessToken;
          if (err) {
            result.err = err.details;
            // Ensure that optional fields are on the object
            result.err.name = result.err.name ? result.err.name : null;
            result.err.code = result.err.code ? result.err.code : null;
            result.err.statusCode = result.err.statusCode ? result.err.statusCode : null;
          }
          if (authResult) {
            result.ok = { profile: profile, token: token };
            localStorage.setItem('profile', JSON.stringify(profile));
            localStorage.setItem('token', token);
          }
          elmApp.ports.auth0authResult.send(result);
        });
        window.location.hash = '';
      }
    });
  </script>
</html>

First we need to add our compiled Elm files as well as the Auth0 lock widget JavaScript file:

<script src="Main.js"></script>
<script src="Auth0.js"></script>
<script src="Authentication.js"></script>
...
<script src="https://cdn.auth0.com/js/auth0/9.0.0/auth0.min.js"></script>

Then we'll create an Auth0 WebAuth instance:

var webAuth = new auth0.WebAuth({
  domain: '[YOUR_AUTH0_DOMAIN]', // e.g., you.auth0.com
  clientID: '[YOUR_AUTH0_CLIENT_ID]',
  scope: 'email',
  responseType: 'token',
  redirectUri: 'http://localhost:8888'
});

Replace

[YOUR_AUTH0_CLIENT_ID]
and
[YOUR_AUTH0_DOMAIN]
with your app client's ID and domain from your Auth0 dashboard Client settings.

Next we'll set up the JS to instantiate the Elm application with flags and ports to interoperate with the lock widget and

localStorage
. We'll request a stored profile and token and if available, we'll recreate an object that matches the record we'll use in the
Auth0.elm
module for a
LoggedInUser
. Then we'll create ports to call the
authorize()
endpoint and log out, adding and removing items from local storage accordingly. Finally, we'll parse the hash and retrieve the profile when the user logs in at the centralized login page and is redirected back to our app, sending the resulting data to our Elm app via an
auth0authResult
port.

Auth0 Module

Next we'll build our

Auth0.elm
module:

-- Auth0.elm

module Auth0
    exposing
        ( AuthenticationState(..)
        , AuthenticationError
        , AuthenticationResult
        , RawAuthenticationResult
        , Options
        , defaultOpts
        , LoggedInUser
        , UserProfile
        , Token
        , mapResult
        )


type alias LoggedInUser =
    { profile : UserProfile
    , token : Token
    }


type AuthenticationState
    = LoggedOut
    | LoggedIn LoggedInUser


type alias Options =
    {}


type alias UserProfile =
    { email : String
    , email_verified : Bool
    }


type alias Token =
    String


type alias AuthenticationError =
    { name : Maybe String
    , code : Maybe String
    , description : String
    , statusCode : Maybe Int
    }


type alias AuthenticationResult =
    Result AuthenticationError LoggedInUser


type alias RawAuthenticationResult =
    { err : Maybe AuthenticationError
    , ok : Maybe LoggedInUser
    }


mapResult : RawAuthenticationResult -> AuthenticationResult
mapResult result =
    case ( result.err, result.ok ) of
        ( Just msg, _ ) ->
            Err msg

        ( Nothing, Nothing ) ->
            Err { name = Nothing, code = Nothing, statusCode = Nothing, description = "No information was received from the authentication provider" }

        ( Nothing, Just user ) ->
            Ok user


defaultOpts : Options
defaultOpts =
    {}

This module handles responses from Auth0 authentication requests. It defines authentication state and types for the user's profile, JWT, authentication errors, and results.

Authentication Module

Now we need a way for the Auth0 module to interface with the authentication logic for our Elm app. We'll set this up in an

Authentication.elm
file:

-- Authentication.elm

module Authentication
    exposing
        ( Msg(..)
        , Model
        , init
        , update
        , handleAuthResult
        , tryGetUserProfile
        , isLoggedIn
        )

import Auth0


type alias Model =
    { state : Auth0.AuthenticationState
    , lastError : Maybe Auth0.AuthenticationError
    , authorize : Auth0.Options -> Cmd Msg
    , logOut : () -> Cmd Msg
    }


init : (Auth0.Options -> Cmd Msg) -> (() -> Cmd Msg) -> Maybe Auth0.LoggedInUser -> Model
init authorize logOut initialData =
    { state =
        case initialData of
            Just user ->
                Auth0.LoggedIn user

            Nothing ->
                Auth0.LoggedOut
    , lastError = Nothing
    , authorize = authorize
    , logOut = logOut
    }


type Msg
    = AuthenticationResult Auth0.AuthenticationResult
    | ShowLogIn
    | LogOut

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        AuthenticationResult result ->
            let
                ( newState, error ) =
                    case result of
                        Ok user ->
                            ( Auth0.LoggedIn user, Nothing )

                        Err err ->
                            ( model.state, Just err )
            in
                ( { model | state = newState, lastError = error }, Cmd.none )

        ShowLogIn ->
            ( model, model.authorize Auth0.defaultOpts )

        LogOut ->
            ( { model | state = Auth0.LoggedOut }, model.logOut () )


handleAuthResult : Auth0.RawAuthenticationResult -> Msg
handleAuthResult =
    Auth0.mapResult >> AuthenticationResult


tryGetUserProfile : Model -> Maybe Auth0.UserProfile
tryGetUserProfile model =
    case model.state of
        Auth0.LoggedIn user ->
            Just user.profile

        Auth0.LoggedOut ->
            Nothing


isLoggedIn : Model -> Bool
isLoggedIn model =
    case model.state of
        Auth0.LoggedIn _ ->
            True

        Auth0.LoggedOut ->
            False

We need to import the

Auth0
module so we can reference it. The model provides a way for our
Main
Elm module to send data into the
Authentication
module. We'll do this by passing arguments to
Authentication
's
init
function from the
Main
module. We'll establish a
Msg
union type and then in our
update
function, we can handle authentication results, call the Auth0
authorize
endpoint to log in at the centralized login page, and log out.

Implementing Auth0 in Main.elm

Our modules are ready to use. We'll import them in our

Main.elm
program file and create our
model
,
update
, and
view
:

-- Main.elm

port module Main exposing (..)

import Html exposing (..)
import Html.Events exposing (..)
import Html.Attributes exposing (..)
import Auth0
import Authentication


main : Program (Maybe Auth0.LoggedInUser) Model Msg
main =
    Html.programWithFlags
        { init = init
        , update = update
        , subscriptions = subscriptions
        , view = view
        }


type alias Model =
    { authModel : Authentication.Model
    }


-- Init

init : Maybe Auth0.LoggedInUser -> ( Model, Cmd Msg )
init initialUser =
    ( Model (Authentication.init auth0authorize auth0logout initialUser), Cmd.none )


-- Messages

type Msg
    = AuthenticationMsg Authentication.Msg


-- Ports

port auth0authorize : Auth0.Options -> Cmd msg
port auth0authResult : (Auth0.RawAuthenticationResult -> msg) -> Sub msg
port auth0logout : () -> Cmd msg


-- Update

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        AuthenticationMsg authMsg ->
            let
                ( authModel, cmd ) =
                    Authentication.update authMsg model.authModel
            in
                ( { model | authModel = authModel }, Cmd.map AuthenticationMsg cmd )



-- Subscriptions

subscriptions : a -> Sub Msg
subscriptions model =
    auth0authResult (Authentication.handleAuthResult >> AuthenticationMsg)


-- View

view : Model -> Html Msg
view model =
    div [ class "container" ]
        [ div [ class "jumbotron text-center" ]
            [ div []
                (case Authentication.tryGetUserProfile model.authModel of
                    Nothing ->
                        [ p [] [ text "Please log in" ] ]

                    Just user ->
                        [ p [] [ text ("Hello, " ++ user.email ++ "!") ] ]
                )
            , p []
                [ button
                    [ class "btn btn-primary"
                    , onClick
                        (AuthenticationMsg
                            (if Authentication.isLoggedIn model.authModel then
                                Authentication.LogOut
                                else
                                Authentication.ShowLogIn
                            )
                        )
                    ]
                    [ text
                        (if Authentication.isLoggedIn model.authModel then
                            "Log Out"
                            else
                            "Log In"
                        )
                    ]
                ]
            ]
        ]

We'll use

programWithFlags
because we want to check for an existing user and token in local storage upon initialization. The model contains the
Authentication
model record we set up earlier.

In the

init
function, we will
init
Authentication
and pass in arguments for the ports that show the lock and log out. We'll also pass the initial user from local storage if available (recall that we set this up in
index.html
to mirror the
LoggedInUser
type from the
Auth0
module).

We need to subscribe to the

auth0authResult
port to listen for external input from Auth0 hash parsing.

Note:

>>
represents function chaining.

Finally, the view displays a message and button to log in if there is no authentication data in storage, and a greeting with the user's email along with a logout button if there is.

Elm: Now and Future

We made a simple app but covered a lot of ground with Elm's architecture, syntax, and implementation of features you'll likely come across in web application development. Authenticating with JWT was straightforward and packages and JS interop offer a lot of extensibility.

Elm began in 2012 as Evan Czaplicki's Harvard senior thesis and it's still a newcomer in the landscape of front-end languages. That isn't stopping production use though: NoRedInk has been compiling Elm to production for almost a year (Introduction to Elm - Richard Feldman) with no runtime exceptions and Evan Czaplicki is deploying Elm to production at Prezi. Elm's compiler offers a lot of test coverage "free of charge" by thoroughly checking all logic and branches. In addition, the Elm Architecture of model-view-update inspired Redux.

Elm also has an active community. I particularly found the elmlang Slack to be a great place to learn about Elm and chat with knowledgeable developers who are happy to help with any questions.

There are a lot of exciting things about Elm and I'm looking forward to seeing how it continues to evolve. Static typing, functional programming, and friendly documentation and compiler messaging make it a clean and speedy coding experience. There's also a peace of mind that Elm provides—the fear of production runtime errors is a thing of the past. Once Elm compiles, it just works, and that is something that no other JavaScript SPA frameworks can offer.