solcates / solcates-elm-oauth2 / OAuth.AuthorizationCode.PKCE

OAuth 2.0 public clients utilizing the Authorization Code Grant are susceptible to the authorization code interception attack. A possible mitigation against the threat is to use a technique called Proof Key for Code Exchange (PKCE, pronounced "pixy") when supported by the target authorization server. See also RFC 7636.

                                     +-----------------+
                                     |  Auth   Server  |
    +-------+                        | +-------------+ |
    |       |--(1)- Auth Request --->| |             | |
    |       |    + code_challenge    | |    Auth     | |
    |       |                        | |   Endpoint  | |
    |       |<-(2)-- Auth Code ------| |             | |
    |  Elm  |                        | +-------------+ |
    |  App  |                        |                 |
    |       |                        | +-------------+ |
    |       |--(3)- Token Request -->| |             | |
    |       |      + code_verifier   | |   Token     | |
    |       |                        | |  Endpoint   | |
    |       |<-(4)- Access Token --->| |             | |
    +-------+                        | +-------------+ |
                                     +-----------------+

See also the Authorization Code flow for details about the basic version of this flow.

Code Verifier / Challenge


type CodeVerifier

An opaque type representing a code verifier. Typically constructed from a high quality entropy.

case codeVerifierFromBytes entropy of
  Nothing -> {- ...-}
  Just codeVerifier -> {- ... -}


type CodeChallenge

An opaque type representing a code challenge. Typically constructed from a CodeVerifier.

let codeChallenge = mkCodeChallenge codeVerifier

codeVerifierFromBytes : Bytes -> Maybe CodeVerifier

Construct a code verifier from a byte sequence generated from a high quality randomness source (i.e. cryptographic).

Ideally, the byte sequence should be 32 or 64 bytes, and it must be at least 32 bytes and at most 90 bytes.

codeVerifierToString : CodeVerifier -> String

Convert a code verifier to its string representation.

mkCodeChallenge : CodeVerifier -> CodeChallenge

Construct a CodeChallenge to send to the authorization server. Upon receiving the authorization code, the client can then the associated CodeVerifier to prove it is the rightful owner of the authorization code.

codeChallengeToString : CodeChallenge -> String

Convert a code challenge to its string representation.

Authorize

makeAuthorizationUrl : Authorization -> Url

Redirects the resource owner (user) to the resource provider server using the specified authorization flow.

parseCode : Url -> AuthorizationResult

Parse the location looking for a parameters set by the resource provider server after redirecting the resource owner (user).

Returns AuthorizationResult Empty when there's nothing.


type alias Authorization =
{ clientId : String
, url : Url
, redirectUri : Url
, scope : List String
, state : Maybe String
, codeChallenge : CodeChallenge 
}

Request configuration for an authorization (Authorization Code & Implicit flows)


type alias AuthorizationCode =
String

A simple type alias to ease readability of type signatures


type AuthorizationResult
    = Empty
    | Error AuthorizationError
    | Success AuthorizationSuccess

Describes errors coming from attempting to parse a url after an OAuth redirection


type alias AuthorizationSuccess =
{ code : String
, state : Maybe String 
}

The response obtained as a result of an authorization


type alias AuthorizationError =
{ error : OAuth.ErrorCode
, errorDescription : Maybe String
, errorUri : Maybe String
, state : Maybe String 
}

Describes an OAuth error as a result of an authorization request failure

Authenticate

makeTokenRequest : (Result Http.Error AuthenticationSuccess -> msg) -> Authentication -> RequestParts msg

Builds a the request components required to get a token from an authorization code

let req : Http.Request AuthenticationSuccess
    req = makeTokenRequest toMsg authentication |> Http.request


type alias Authentication =
{ credentials : Credentials
, code : String
, codeVerifier : CodeVerifier
, redirectUri : Url
, url : Url 
}

Request configuration for an AuthorizationCode authentication


type alias Credentials =
{ clientId : String
, secret : Maybe String 
}

Describes at least a clientId and if define, a complete set of credentials with the secret. The secret is so-to-speak optional and depends on whether the authorization server you interact with requires a 'Basic' authentication on top of the authentication request. Provides it if you need to do so.

  { clientId = "<my-client-id>"
  , secret = Just "<my-client-secret>"
  }


type alias AuthenticationSuccess =
{ token : OAuth.Token
, refreshToken : Maybe OAuth.Token
, expiresIn : Maybe Basics.Int
, scope : List String 
}

The response obtained as a result of an authentication (implicit or not)


type alias AuthenticationError =
{ error : OAuth.ErrorCode
, errorDescription : Maybe String
, errorUri : Maybe String 
}

Describes an OAuth error as a result of a request failure


type alias RequestParts a =
{ method : String
, headers : List Http.Header
, url : String
, body : Http.Body
, expect : Http.Expect a
, timeout : Maybe Basics.Float
, tracker : Maybe String 
}

Parts required to build a request. This record is given to Http.request in order to create a new request and may be adjusted at will.

JSON Decoders

defaultAuthenticationSuccessDecoder : Json.Decode.Decoder AuthenticationSuccess

Json decoder for a positive response. You may provide a custom response decoder using other decoders from this module, or some of your own craft.

defaultAuthenticationSuccessDecoder : Decoder AuthenticationSuccess
defaultAuthenticationSuccessDecoder =
    D.map4 AuthenticationSuccess
        tokenDecoder
        refreshTokenDecoder
        expiresInDecoder
        scopeDecoder

defaultAuthenticationErrorDecoder : Json.Decode.Decoder AuthenticationError

Json decoder for an errored response.

case res of
    Err (Http.BadStatus { body }) ->
        case Json.decodeString OAuth.AuthorizationCode.defaultAuthenticationErrorDecoder body of
            Ok { error, errorDescription } ->
                doSomething

            _ ->
                parserFailed

    _ ->
        someOtherError

JSON Decoders (advanced)

defaultExpiresInDecoder : Json.Decode.Decoder (Maybe Basics.Int)

Json decoder for an 'expire' timestamp

defaultScopeDecoder : Json.Decode.Decoder (List String)

Json decoder for a 'scope'

lenientScopeDecoder : Json.Decode.Decoder (List String)

Json decoder for a 'scope', allowing comma- or space-separated scopes

defaultTokenDecoder : Json.Decode.Decoder OAuth.Token

Json decoder for an 'access_token'

defaultRefreshTokenDecoder : Json.Decode.Decoder (Maybe OAuth.Token)

Json decoder for a 'refresh_token'

defaultErrorDecoder : Json.Decode.Decoder OAuth.ErrorCode

Json decoder for 'error' field

defaultErrorDescriptionDecoder : Json.Decode.Decoder (Maybe String)

Json decoder for 'error_description' field

defaultErrorUriDecoder : Json.Decode.Decoder (Maybe String)

Json decoder for 'error_uri' field

Query Parsers (advanced)

parseCodeWith : Parsers -> Url -> AuthorizationResult

See parseCode, but gives you the ability to provide your own custom parsers.


type alias Parsers =
{ codeParser : Url.Parser.Query.Parser (Maybe String)
, errorParser : Url.Parser.Query.Parser (Maybe OAuth.ErrorCode)
, authorizationSuccessParser : String -> Url.Parser.Query.Parser AuthorizationSuccess
, authorizationErrorParser : OAuth.ErrorCode -> Url.Parser.Query.Parser AuthorizationError 
}

Parsers used in the 'parseCode' function.

defaultParsers : Parsers

Default parsers according to RFC-6749

defaultCodeParser : Url.Parser.Query.Parser (Maybe String)

Default 'code' parser according to RFC-6749

defaultErrorParser : Url.Parser.Query.Parser (Maybe OAuth.ErrorCode)

Default 'error' parser according to RFC-6749

defaultAuthorizationSuccessParser : String -> Url.Parser.Query.Parser AuthorizationSuccess

Default response success parser according to RFC-6749

defaultAuthorizationErrorParser : OAuth.ErrorCode -> Url.Parser.Query.Parser AuthorizationError

Default response error parser according to RFC-6749