This library is a work in progress! We hope to clean up the API some more before a formal release.
It is however entirely functional, and being used in a number of production Lamdera apps, including the entire login mechanism for https://dashboard.lamdera.app/.
- You can see a draft implementation of lamdera/auth replacing a fake user/pass auth with a real Github Auth on the Lamdera Realword repo.
- There's also a vanilla Lamdera app example thanks to @vpjapp: https://github.com/vpjapp/lamdera-auth-example
Progress:
- OAuth flow
- Email magic link flow
- URL route handling
- Logout handling
- Improve the API surface area
- Actually make this an installable package with nice docs
- Stretch goal: elm-review rule to set everything up!
This package vendors two other Elm packages in order to make modifications:
truqu/elm-oauth2
: license (MIT), modification notesleojpod/elm-jwt
: license (GPLv3), modification notes
Ideally these will be de-vendored into a regular Elm dependencies in future.
Until this is available as a package:
- Clone this repo into your project as a git submodule (or vendor it manually by copy pasting src)
- Reference
src
in your project'selm.json:source-directories
- Install the relevant deps:
yes | lamdera install elm/browser
yes | lamdera install elm/bytes
yes | lamdera install elm/http
yes | lamdera install elm/json
yes | lamdera install elm/regex
yes | lamdera install elm/time
yes | lamdera install elm/url
yes | lamdera install elm-community/dict-extra
yes | lamdera install elm-community/list-extra
yes | lamdera install chelovek0v/bbase64
yes | lamdera install ktonon/elm-crypto
yes | lamdera install ktonon/elm-word
yes | lamdera install NoRedInk/elm-json-decode-pipeline
yes | lamdera install TSFoster/elm-sha1
You might also have luck with elm-git-install, though its not been tried yet.
- Create
src/Auth.elm
:
module Auth exposing (..)
import Auth.Common
import Auth.Method.EmailMagicLink
import Auth.Method.OAuthGithub
import Auth.Method.OAuthGoogle
import Lamdera
import Types exposing (..)
config : Auth.Common.Config FrontendMsg ToBackend BackendMsg ToFrontend FrontendModel BackendModel
config =
{ toBackend = AuthToBackend
, toFrontend = AuthToFrontend
, backendMsg = AuthBackendMsg
, sendToFrontend = Lamdera.sendToFrontend
, sendToBackend = Lamdera.sendToBackend
, methods =
[ Auth.Method.EmailMagicLink.configuration
, Auth.Method.OAuthGithub.configuration Config.githubAppClientId Config.githubAppClientSecret
, Auth.Method.OAuthGoogle.configuration Config.googleAppClientId Config.googleAppClientSecret
]
}
- Modify the 2 core Model types in
src/Types.elm
:
import Auth.Common
import Dict exposing (Dict)
import Lamdera
import Url exposing (Url)
type alias FrontendModel =
{ ...
, authFlow : Auth.Common.Flow
, authRedirectBaseUrl : Url
}
type alias BackendModel =
{ ...
, pendingAuths : Dict Lamdera.SessionId Auth.Common.PendingAuth
}
- Modify the 4 core Msg types in
src/Types.elm
:
import Auth.Common
type FrontendMsg
...
| AuthSigninRequested { methodId : Auth.Common.MethodId, username : Maybe String }
type ToBackend
...
| AuthToBackend Auth.Common.ToBackend
type BackendMsg
...
| AuthBackendMsg Auth.Common.BackendMsg
type ToFrontend
...
| AuthToFrontend Auth.Common.ToFrontend
- Implement the 4 new Msg variants:
Frontend.elm
:
update msg model =
...
AuthSigninRequested { methodId, username } ->
Auth.Flow.signInRequested methodId model username
|> Tuple.mapSecond (AuthToBackend >> sendToBackend)
updateFromBackend msg model =
...
AuthToFrontend authToFrontendMsg ->
Auth.updateFromBackend authToFrontendMsg model
Backend.elm
:
update msg model =
...
AuthBackendMsg authMsg ->
Auth.Flow.backendUpdate (Auth.backendConfig model) authMsg
updateFromFrontend sessionId clientId msg model =
...
AuthToBackend authToBackend ->
Auth.Flow.updateFromFrontend (Auth.backendConfig model) clientId sessionId authToBackend model
- Adjust your routing handlers:
How you do page routing will vary on your app approach (i.e. manual, elm-land, etc), but here's an example route matcher using elm/url:Url.Parser
:
map LoginCallback (s "login" </> string </> s "callback")
Add the additional Msg variant to your FrontendMsg:
type FrontendMsg =
...
| LoginCallback String
And add the handler to your update function:
update msg model =
...
LoginCallback methodId ->
Auth.Flow.init model
methodId
url
key
(\msg -> Lamdera.sendToBackend (AuthToBackend msg))