This is an alternative site for discovering Elm packages. You may be looking for the official Elm package site instead.
OAuth 2.0 client-side utils
version 2.2.1
license MIT
native-modules False
elm-version 0.18.0 <= v < 0.19.0
Tag 2.2.1
Committed At 2018-08-16 08:23:58 UTC
truqu/elm-base64 2.0.0 <= v < 3.0.0 2.0.4
elm-lang/navigation 2.1.0 <= v < 3.0.0 2.1.0
elm-lang/http 1.0.0 <= v < 2.0.0 1.0.0
elm-lang/html 2.0.0 <= v < 3.0.0 2.0.0
elm-lang/core 5.1.1 <= v < 6.0.0 5.1.1
Bogdanp/elm-querystring 1.0.0 <= v < 2.0.0 1.0.0

README

Elm OAuth 2

This package offers some utilities to implement a client-side OAuth 2 authorization in Elm. It covers all 4 grant types:

  • Implicit: The most commonly used. The token is obtained directly as a result of a user redirection to an OAuth provider.

  • Authorization Code: The token is obtained as a result of an authentication, from a code obtained as a result of a user redirection to an OAuth provider.

  • Resource Owner Password Credentials: The token is obtained directly by exchanging the user credentials with an OAuth provider.

  • Client Credentials: The token is obtained directly by exchanging application credentials with an OAuth provider.

Getting Started

The following parts is a walkthrough the first 2 flows. The last 2 are actually pretty straightforward and can be seen (in terms of steps) as a subset of the Authorization Code flow.

Installation

elm package install truqu/elm-oauth2

Usage (Implicit Flow)

A complete example is available here (with the corresponding sources here)

Imports
import OAuth
import OAuth.Implicit
Authorizing & Authenticating
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        NoOp ->
            model ! []

        Authorize ->
            model
                ! [ OAuth.Implicit.authorize
                        { clientId = "clientId"
                        , redirectUri = "redirectUri"
                        , responseType = OAuth.Token -- Use the OAuth.Token response type
                        , scope = [ "whatever" ]
                        , state = Nothing
                        , url = "authorizationEndpoint"
                        }
                  ]
Parsing the token
init : Navigation.Location -> ( Model, Cmd Msg )
init location =
    let
        model = {}
    in
        case OAuth.Implicit.parse location of
            -- A token has been parsed 
            Ok { token } ->
                { model | token = Just token } ! [] 

            -- Nothing to parse, unauthenticated
            Err OAuth.Empty ->
                model ! []

            -- An other type of error (invalid parsing or an actual OAuth error) 
            Err _ ->
                model ! []
Using the token
let
    req =
        Http.request
            { method = "GET"
            , body = Http.emptyBody
            , headers = OAuth.use token [] -- Add the token to the http headers
            , withCredentials = False
            , url = "whatever"
            , expect = Http.expectJson decoder
            , timeout = Nothing
            }
in
    { model | token = Just token } ! [ Http.send handleResponse req ]

Usage (Authorization Code Flow)

A complete example is available here (with the corresponding sources here)

Imports
import OAuth
import OAuth.AuthorizationCode
Authorizing & Authenticating
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        NoOp ->
            model ! []

        Authorize ->
            model
                ! [ OAuth.AuthorizationCode.authorize
                        { clientId = "clientId"
                        , redirectUri = "redirectUri"
                        , responseType = OAuth.Code -- Use the OAuth.Code response type
                        , scope = [ "whatever" ]
                        , state = Nothing
                        , url = "authorizationEndpoint"
                        }
                  ]

        Authenticate res ->
            case res of
                -- Http request didn't go through
                Err err ->
                  model ! []

                -- Token received from the server
                Ok { token } ->

Parsing the token
init : Navigation.Location -> ( Model, Cmd Msg )
init location =
    let
        model = {}
    in
        case OAuth.AuthorizationCode.parse location of
            -- A token has been parsed 
            Ok { code } ->
                let 
                    req =
                        OAuth.AuthorizationCode.authenticate <|
                            OAuth.AuthorizationCode
                                { credentials = { clientId = "clientId", secret = "secret" }
                                , code = code
                                , redirectUri = "redirectUri"
                                , scope = [ "whatever" ]
                                , state = Nothing
                                , url = "tokenEndpoint"
                                }
                in
                    model [ Http.send Authenticate req ]

            -- Nothing to parse, unauthenticated
            Err OAuth.Empty ->
                model ! []

            -- An other type of error (invalid parsing or an actual OAuth error) 
            Err _ ->
                model ! []
Using the token
let
    req =
        Http.request
            { method = "GET"
            , body = Http.emptyBody
            , headers = OAuth.use token [] -- Add the token to the http headers
            , withCredentials = False
            , url = "whatever"
            , expect = Http.expectJson decoder
            , timeout = Nothing
            }
in
    { model | token = Just token } ! [ Http.send handleResponse req ]

TroubleShooting

Interacting with GitHub

GitHub API v3 supports the Authorization Code Flow in order to obtain access tokens for a registered application. However, the implementation presents a flaw which makes it not compliant with the official RFC:

  • By default, GitHub's authorization server will respond with a x-www-form-urlencoded mime-type when trying to exchange the authorization code against an access token (instead of a application/json mime-type as specified in the official RFC).

  • This behavior can be changed by providing an extra Accept: application/json header with the authenticate request. However, by doing so, GitHub's authorization server will encode the scope of the response as a comma-separated list (instead of a space-separated list as specified in the official RFC).

Hence, this library provides a way to work around this implementation quirks by adjusting the authentication request before it gets sent. To achieve this, one may use the various decoders now exposed in OAuth.Decode to craft a custom transformation function for the authenticateWithOpts functions.

Here's a small example of how to work around GitHub's API v3 implementation:

lenientResponseDecoder : Json.Decoder ResponseToken
lenientResponseDecoder =
    Json.map5 OAuth.Decode.makeResponseToken
        OAuth.Decode.accessTokenDecoder
        OAuth.Decode.expiresInDecoder
        OAuth.Decode.refreshTokenDecoder
        OAuth.Decode.lenientScopeDecoder
        OAuth.Decode.stateDecoder


adjustRequest : AdjustRequest ResponseToken
adjustRequest req =
    let
        headers =
            Http.header "Accept" ("application/json")  :: req.headers

        expect =
            Http.expectJson lenientResponseDecoder
    in
        { req | headers = headers, expect = expect }


getToken : String -> Cmd ResponseToken
getToken code =
    let
        req =
            OAuth.AuthorizationCode.authenticateWithOpts adjustRequest <|
                OAuth.AuthorizationCode
                    { credentials = { clientId = "clientId", secret = "secret" }
                    , code = code
                    , redirectUri = "redirectUri"
                    , scope = [ "whatever" ]
                    , state = Nothing
                    , url = "tokenEndpoint"
                    }
    in
        Http.send handleResponse req

Changelog

CHANGELOG.md